def test_get_metadata(video_path, exp_metadata): metadata = services.get_metadata(video_path=video_path) assert metadata == exp_metadata assert sorted(metadata.keys()) == sorted( ('audio_stream', 'video_stream', 'summary', 'format') )
def make(**kwargs): video = video_factory( video_path=VIDEO_PATH, description='description', tags=['tag1', 'tag2'], visibility='draft', title='title', **kwargs, ) file_ = simple_uploaded_file_factory(video_path=VIDEO_PATH) models.VideoRendition.objects.create( video=video, file=file_, playlist_file=rendition_playlist_file, file_size=1000, width=256, height=144, duration=10, ext='webm', container='webm', audio_codec='opus', video_codec='vp9', profile='mpeg4_144p', name='mpeg4_144p', framerate=30, metadata=media_services.get_metadata(VIDEO_PATH), ) return video
def test_transcode_profile_does_apply(video_filename, profile_cls, exp_result): video_path = constants.TEST_DATA_DIR / video_filename metadata_summary = services.get_metadata(video_path)['summary'] result = manager._transcode_profile_does_apply( profile_cls=profile_cls, metadata_summary=metadata_summary) assert result == exp_result
def test_generate_default_thumbnail_image(tmpdir): image_path = TEST_DATA_DIR / 'thumbnail-vertical.jpg' test_image_path = tmpdir / 'thumbnail-vertical.jpg' shutil.copyfile(image_path, test_image_path) result_image = services._generate_default_thumbnail_image( image_path=test_image_path ) assert result_image.exists() metadata = services.get_metadata(result_image) assert metadata['summary']['width'] == 1280 assert metadata['summary']['height'] == 720
def video_with_renditions_and_segments(video, simple_uploaded_file, tmpdir): video_renditions_to_create = ( (640, 360, constants.VID_360P_24FPS, False), (640, 360, constants.VID_360P_24FPS, True), (1920, 1080, constants.VIDEO_PATH_1080_60FPS, True), ) for ( width, height, video_path, create_segments, ) in video_renditions_to_create: video_rendition = models.VideoRendition.objects.create( video=video, file=simple_uploaded_file, name=f'mpeg4_{height}p', profile=f'mpeg4_{height}p', ext='webm', framerate=30, file_size=1, width=width, height=height, metadata=media_services.get_metadata(video_path), codecs_string='avc1.640028,mp4a.40.2', ) assert not video_rendition.playlist_file if not create_segments: continue video_path = constants.VIDEO_PATH_1080_30FPS_VERT profile = transcoder_profiles.MpegFour360p ( segments_playlist_file, segment_paths, _, ) = ffmpeg._create_segments_for_video( video_path=video_path, profile=profile, tmp_dir=tmpdir, video_rendition_id=video_rendition.id, video_id=video_rendition.video_id, ) media_services.persist_video_rendition_segments( video_rendition=video_rendition, segments_playlist_file=segments_playlist_file, segments=segment_paths, ) return video, video_renditions_to_create
def test_ffmpeg_generate_thumbnails(tmpdir): video_path = Path(tmpdir / constants.VID_1920_X_960.name) shutil.copyfile(constants.VID_1920_X_960, video_path) thumbnails = ffmpeg._ffmpeg_generate_thumbnails( video_file_path=video_path, profile=transcoder_profiles.MpegFour720p, ) assert thumbnails for offset, thumb_path in thumbnails: assert offset > 0 assert thumb_path.exists() metadata = services.get_metadata(thumb_path) # Height is fixed and width is auto. assert metadata['summary']['height'] == 720 assert metadata['summary']['width'] == 1440
def test_create_segments_for_video(tmpdir, video_with_renditions_and_segments): video_path = constants.VIDEO_PATH_1080_30FPS_VERT profile = transcoder_profiles.Webm360p video, _ = video_with_renditions_and_segments video_rendition = video.videorendition_set.first() video_rendition_id = video_rendition.id video_id = video_rendition.video_id ( segments_playlist_file, segment_paths, codecs_string, ) = ffmpeg._create_segments_for_video( video_path=video_path, profile=profile, tmp_dir=tmpdir, video_rendition_id=video_rendition_id, video_id=video_id, ) exp_num_segments = 13 assert len(segment_paths) == exp_num_segments assert all(p.exists() and isinstance(p, Path) for p in segment_paths) assert isinstance(segments_playlist_file, Path) assert segments_playlist_file.exists() assert codecs_string == 'avc1.640028,mp4a.40.2' segment_names_durations = [ (p.name, services.get_metadata(p)['summary']['duration']) for p in segment_paths ] exp_segment_names_durations = [ ('0.ts', 9.2092), ('1.ts', 3.3033), ('2.ts', 4.337667), ('3.ts', 7.540867), ('4.ts', 4.537867), ('5.ts', 4.6046), ('6.ts', 6.006), ('7.ts', 8.341667), ('8.ts', 3.036367), ('9.ts', 6.773433), ('10.ts', 5.472133), ('11.ts', 8.341667), ('12.ts', 5.9059), ] assert segment_names_durations == exp_segment_names_durations exp_playlist = f""" #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:9 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:9.209200, /videos/{video_id}/renditions/{video_rendition_id}/segments/0.ts #EXTINF:3.303300, /videos/{video_id}/renditions/{video_rendition_id}/segments/1.ts #EXTINF:4.337667, /videos/{video_id}/renditions/{video_rendition_id}/segments/2.ts #EXTINF:7.540867, /videos/{video_id}/renditions/{video_rendition_id}/segments/3.ts #EXTINF:4.537867, /videos/{video_id}/renditions/{video_rendition_id}/segments/4.ts #EXTINF:4.604600, /videos/{video_id}/renditions/{video_rendition_id}/segments/5.ts #EXTINF:6.006000, /videos/{video_id}/renditions/{video_rendition_id}/segments/6.ts #EXTINF:8.341667, /videos/{video_id}/renditions/{video_rendition_id}/segments/7.ts #EXTINF:3.036367, /videos/{video_id}/renditions/{video_rendition_id}/segments/8.ts #EXTINF:6.773433, /videos/{video_id}/renditions/{video_rendition_id}/segments/9.ts #EXTINF:5.472133, /videos/{video_id}/renditions/{video_rendition_id}/segments/10.ts #EXTINF:8.341667, /videos/{video_id}/renditions/{video_rendition_id}/segments/11.ts #EXTINF:5.905900, /videos/{video_id}/renditions/{video_rendition_id}/segments/12.ts #EXT-X-ENDLIST """ assert (m3u8.load(str(segments_playlist_file)).dumps() == m3u8.loads( exp_playlist).dumps())
def test_create_segments_for_video( tmpdir, settings, video_with_renditions_and_segments ): video_path = constants.VIDEO_PATH_1080_30FPS_VERT profile = transcoder_profiles.MpegFour360p video, _ = video_with_renditions_and_segments video_rendition = video.renditions.first() video_rendition_id = video_rendition.id video_id = video_rendition.video_id ( segments_playlist_file, segment_paths, codecs_string, ) = ffmpeg._create_segments_for_video( video_path=video_path, profile=profile, tmp_dir=tmpdir, video_rendition_id=video_rendition_id, video_id=video_id, ) exp_num_segments = 13 assert len(segment_paths) == exp_num_segments assert all(p.exists() and isinstance(p, Path) for p in segment_paths) assert isinstance(segments_playlist_file, Path) assert segments_playlist_file.exists() assert codecs_string == 'avc1.640028,mp4a.40.2' segment_names_durations = [ (p.name, services.get_metadata(p)['summary']['duration']) for p in segment_paths ] exp_segment_names_durations = [ ('0.ts', 9.2092), ('1.ts', 3.3033), ('2.ts', 4.337667), ('3.ts', 7.540867), ('4.ts', 4.537867), ('5.ts', 4.6046), ('6.ts', 6.006), ('7.ts', 8.341667), ('8.ts', 3.036367), ('9.ts', 6.773433), ('10.ts', 5.472133), ('11.ts', 8.341667), ('12.ts', 5.9059), ] for actual, expected in zip( segment_names_durations, exp_segment_names_durations ): actual_name, actual_duration = actual expected_name, expected_duration = expected assert actual_name == expected_name assert actual_duration == pytest.approx(expected_duration, 0.5) bucket = settings.BUCKET_MEDIA exp_playlist = f""" #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:9 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:9.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/0.ts #EXTINF:3.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/1.ts #EXTINF:4.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/2.ts #EXTINF:7.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/3.ts #EXTINF:4.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/4.ts #EXTINF:4.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/5.ts #EXTINF:6.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/6.ts #EXTINF:8.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/7.ts #EXTINF:3.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/8.ts #EXTINF:6.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/9.ts #EXTINF:5.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/10.ts #EXTINF:8.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/11.ts #EXTINF:5.01, /{bucket}/videos/{video_id}/renditions/{video_rendition_id}/segments/12.ts #EXT-X-ENDLIST """ actual = m3u8.load(str(segments_playlist_file)).dumps() expected = m3u8.loads(exp_playlist).dumps().replace('.01', '.+') assert re.match(expected, actual)
def test_get( self, api_client_no_auth, video_factory, transcode_job_factory, mocker, simple_uploaded_file_factory, rendition_playlist_file, ): video = video_factory( video_path=VIDEO_PATH, description='description', tags=['tag1', 'tag2'], visibility='draft', title='title', ) file_ = simple_uploaded_file_factory(video_path=VIDEO_PATH) models.VideoRendition.objects.create( video=video, file=file_, playlist_file=rendition_playlist_file, file_size=1000, width=256, height=144, duration=10, ext='webm', container='webm', audio_codec='opus', video_codec='vp9', name='144p', framerate=30, metadata=services.get_metadata(VIDEO_PATH), ) response = api_client_no_auth.get( f'/api/v1/video/{video.id}/playlist.m3u8') assert response.status_code == OK resp_text = response.content.decode() assert resp_text.startswith('#EXTM3U') assert m3u8.loads(resp_text).data == { 'iframe_playlists': [], 'is_endlist': False, 'is_i_frames_only': False, 'is_independent_segments': False, 'is_variant': True, 'keys': [], 'media': [], 'media_sequence': None, 'part_inf': {}, 'playlist_type': None, 'playlists': [{ 'stream_info': { 'bandwidth': 182464, 'closed_captions': 'NONE', 'program_id': 1, 'resolution': '256x144', }, 'uri': mocker.ANY, }], 'rendition_reports': [], 'segments': [], 'session_data': [], 'session_keys': [], 'skip': {}, }