Skip to content

Commit

Permalink
Merge pull request #150 from edx/mrehan/videos-missing-hls
Browse files Browse the repository at this point in the history
Get videos that are missing HLS encodes
  • Loading branch information
Qubad786 authored Nov 13, 2018
2 parents cca22cd + 0f630c7 commit 6e3bb01
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 3 deletions.
124 changes: 122 additions & 2 deletions edxval/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
Tests for Video Abstraction Layer views
"""
import json
from urlparse import urljoin
from urllib import urlencode

from ddt import data, ddt, unpack
from django.core.urlresolvers import reverse
from rest_framework import status

from edxval.models import (CourseVideo, Profile,
TranscriptProviderType, Video, VideoTranscript)
from edxval.models import (
CourseVideo, Profile, TranscriptProviderType,
Video, VideoTranscript, EncodedVideo
)
from edxval.serializers import TranscriptSerializer
from edxval.tests import APIAuthTestCase, constants
from edxval.utils import TranscriptFormat
Expand Down Expand Up @@ -930,3 +934,119 @@ def test_transcript_status(self, patch_data, message, status_code):
response = self.client.patch(self.url, patch_data, format='json')
self.assertEqual(response.status_code, status_code)
self.assertEqual(response.data.get('message'), message)


@ddt
class HLSMissingVideoViewTest(APIAuthTestCase):
"""
HLSMissingVideoView Tests.
"""
def setUp(self):
"""
Tests setup.
"""
self.url = reverse('hls-missing-video')

desktop_profile, __ = Profile.objects.get_or_create(profile_name=constants.PROFILE_DESKTOP)
hls_profile, __ = Profile.objects.get_or_create(profile_name=constants.PROFILE_HLS)

# Setup 2 videos without hls profile
video_wo_hls1 = Video.objects.create(**dict(
constants.VIDEO_DICT_FISH, edx_video_id='video-wo-hls1', status='file_complete'
))
video_wo_hls2 = Video.objects.create(**dict(
constants.VIDEO_DICT_FISH, edx_video_id='video-wo-hls2', status='file_complete'
))
EncodedVideo.objects.create(
video=video_wo_hls1,
profile=desktop_profile,
**constants.ENCODED_VIDEO_DICT_DESKTOP
)
EncodedVideo.objects.create(
video=video_wo_hls2,
profile=desktop_profile,
**constants.ENCODED_VIDEO_DICT_DESKTOP
)

# Setup 2 videos with hls profile
video_w_hls1 = Video.objects.create(**dict(
constants.VIDEO_DICT_FISH, edx_video_id='video-w-hls1', status='file_complete'
))
video_w_hls2 = Video.objects.create(**dict(
constants.VIDEO_DICT_FISH, edx_video_id='video-w-hls2', status='file_complete'
))
EncodedVideo.objects.create(
video=video_w_hls1,
profile=hls_profile,
**constants.ENCODED_VIDEO_DICT_HLS
)
EncodedVideo.objects.create(
video=video_w_hls2,
profile=hls_profile,
**constants.ENCODED_VIDEO_DICT_HLS
)

# Put `video_wo_hls1` into two courses
course_id1, course_id2 = 'test-course-1', 'test-course-2'
CourseVideo.objects.create(video=video_wo_hls1, course_id=course_id1)
CourseVideo.objects.create(video=video_wo_hls1, course_id=course_id2)

super(HLSMissingVideoViewTest, self).setUp()

@data(
(1, 0),
(2, 1),
(2, 0),
(2, 2),
)
@unpack
def test_videos_list_missing_hls_encodes(self, batch_size, offset):
"""
Test that videos that are missing HLS encodes are returned correctly.
"""
expected_video_ids = ['video-wo-hls1', 'video-wo-hls2']
endpoint = urljoin(self.url, '?{}'.format(urlencode({'batch_size': batch_size, 'offset': offset})))
response = self.client.get(endpoint)
response = json.loads(response.content)
self.assertEqual(response['videos'], expected_video_ids[offset: offset+batch_size])

def test_videos_list_missing_hls_encodes_for_courses(self):
"""
Test that videos that are missing HLS encodes are returned correctly for the specified courses.
"""
expected_video_ids = ['video-wo-hls1']
endpoint = urljoin(self.url, '?{}'.format(urlencode({
'courses': ['test-course-1', 'test-course-2']
}, True)))
response = self.client.get(endpoint)
response = json.loads(response.content)
self.assertEqual(response['videos'], expected_video_ids)

def test_update_hls_encodes_for_video(self):
"""
Test that the encode profile gets updated successfully.
"""
expected_data = {
'edx_video_id': 'video-w-hls1',
'profile': 'hls',
'encode_data': {
'file_size': 12,
'bitrate': 12,
'url': 'foo.com/abcd.m3u8'
}
}
response = self.client.post(self.url, expected_data, format='json')
# Assert the success response.
self.assertEqual(response.status_code, 200)

encoded_videos = EncodedVideo.objects.filter(
video__edx_video_id=expected_data['edx_video_id'],
profile__profile_name=expected_data['profile']
)
actual_encoded_video = encoded_videos.first()

# Assert the updated profile and be sure its the only one.
self.assertEqual(encoded_videos.count(), 1)
self.assertEqual(actual_encoded_video.url, expected_data['encode_data']['url'])
self.assertEqual(actual_encoded_video.file_size, expected_data['encode_data']['file_size'])
self.assertEqual(actual_encoded_video.bitrate, expected_data['encode_data']['bitrate'])
5 changes: 5 additions & 0 deletions edxval/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
views.VideoStatusView.as_view(),
name='video-status-update'
),
url(
r'^videos/missing-hls/$',
views.HLSMissingVideoView.as_view(),
name='hls-missing-video'
),
url(
r'^videos/video-transcripts/create/$',
views.VideoTranscriptView.as_view(),
Expand Down
71 changes: 71 additions & 0 deletions edxval/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from edxval.api import create_or_update_video_transcript
from edxval.models import (
CourseVideo,
EncodedVideo,
Profile,
TranscriptProviderType,
Video,
VideoImage,
Expand Down Expand Up @@ -258,3 +260,72 @@ def post(self, request):
)

return Response()


class HLSMissingVideoView(APIView):
"""
A View to list video ids which are missing HLS encodes and update an encode profile for a video.
"""
authentication_classes = (OAuth2Authentication, SessionAuthentication)

def get(self, request):
courses = request.query_params.getlist('courses')
batch_size = int(request.query_params.get('batch_size', 50))
offset = int(request.query_params.get('offset', 0))
if courses:
videos = (CourseVideo.objects.select_related('video')
.prefetch_related('video__encoded_videos', 'video__encoded_videos__profile')
.filter(course_id__in=courses, video__status='file_complete')
.exclude(video__encoded_videos__profile__profile_name='hls')
.values_list('video__edx_video_id', flat=True)
.distinct())

response = Response({'videos': videos}, status=status.HTTP_200_OK)
else:
videos = (Video.objects.prefetch_related('encoded_videos', 'encoded_videos__profile')
.filter(status='file_complete')
.exclude(encoded_videos__profile__profile_name='hls')
.order_by('id')
.values_list('edx_video_id', flat=True)
.distinct())

response = Response(
{
'videos': videos[offset: offset+batch_size],
'total': videos.count(),
'offset': offset,
'batch_size': batch_size,
},
status=status.HTTP_200_OK
)

return response

def post(self, request):
"""
Update a single profile for a given video.
Example request data:
{
'edx_video_id': '1234'
'profile': 'hls',
'encode_data': {
'url': 'foo.com/qwe.m3u8'
'file_size': 34
'bitrate': 12
}
}
"""
edx_video_id = request.data['edx_video_id']
profile = request.data['profile']
encode_data = request.data['encode_data']

video = Video.objects.get(edx_video_id=edx_video_id)
profile = Profile.objects.get(profile_name=profile)

# Delete existing similar profile if its present and
# create new one with updated data.
EncodedVideo.objects.filter(video=video, profile=profile).delete()
EncodedVideo.objects.create(video=video, profile=profile, **encode_data)

return Response(status=status.HTTP_200_OK)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def load_requirements(*requirements_paths):

setup(
name='edxval',
version='0.1.21',
version='0.1.22',
author='edX',
url='http://github.com/edx/edx-val',
description='edx-val',
Expand Down

0 comments on commit 6e3bb01

Please sign in to comment.