Exemplo n.º 1
0
    def create(self, validated_data):
        """
        Create the video and its nested resources.
        """
        courses = validated_data.pop("courses", [])
        encoded_videos = validated_data.pop("encoded_videos", [])
        subtitles = validated_data.pop("subtitles", [])

        video = Video.objects.create(**validated_data)

        EncodedVideo.objects.bulk_create(
            EncodedVideo(video=video, **video_data)
            for video_data in encoded_videos)

        Subtitle.objects.bulk_create(
            Subtitle(video=video, **subtitle_data)
            for subtitle_data in subtitles)

        # The CourseSerializer will already have converted the course data
        # to CourseVideo models, so we can just set the video and save.
        # Also create VideoImage objects if an image filename is present
        for course_video, image_name in courses:
            course_video.video = video
            course_video.save()
            if image_name:
                VideoImage.create_or_update(course_video, image_name)

        return video
Exemplo n.º 2
0
    def update(self, instance, validated_data):
        """
        Update an existing video resource.
        """
        instance.status = validated_data["status"]
        instance.client_video_id = validated_data["client_video_id"]
        instance.duration = validated_data["duration"]
        instance.save()

        # Set encoded videos
        instance.encoded_videos.all().delete()
        EncodedVideo.objects.bulk_create(
            EncodedVideo(video=instance, **video_data)
            for video_data in validated_data.get("encoded_videos", []))

        # Set subtitles
        instance.subtitles.all().delete()
        Subtitle.objects.bulk_create(
            Subtitle(video=instance, **subtitle_data)
            for subtitle_data in validated_data.get("subtitles", []))

        # Set courses
        # NOTE: for backwards compatibility with the DRF v2 behavior,
        # we do NOT delete existing course videos during the update.
        # Also update VideoImage objects if an image filename is present
        for course_video, image_name in validated_data.get("courses", []):
            course_video.video = instance
            course_video.save()
            if image_name:
                VideoImage.create_or_update(course_video, image_name)

        return instance
Exemplo n.º 3
0
    def update(self, instance, validated_data):
        """
        Update an existing video resource.
        """
        instance.status = validated_data["status"]
        instance.client_video_id = validated_data["client_video_id"]
        instance.duration = validated_data["duration"]
        instance.save()

        # Set encoded videos
        instance.encoded_videos.all().delete()
        EncodedVideo.objects.bulk_create(
            EncodedVideo(video=instance, **video_data)
            for video_data in validated_data.get("encoded_videos", [])
        )

        # Set courses
        # NOTE: for backwards compatibility with the DRF v2 behavior,
        # we do NOT delete existing course videos during the update.
        # Also update VideoImage objects if an image filename is present
        for course_video, image_name in validated_data.get("courses", []):
            course_video.video = instance
            course_video.save()
            if image_name:
                VideoImage.create_or_update(course_video, image_name)

        return instance
Exemplo n.º 4
0
def copy_course_videos(source_course_id, destination_course_id):
    """
    Adds the destination_course_id to the videos taken from the source_course_id

    Args:
        source_course_id: The original course_id
        destination_course_id: The new course_id where the videos will be copied
    """
    if source_course_id == destination_course_id:
        return

    course_videos = CourseVideo.objects.select_related('video', 'video_image').filter(
        course_id=str(source_course_id)
    )

    for course_video in course_videos:
        destination_course_video, __ = CourseVideo.objects.get_or_create(
            video=course_video.video,
            course_id=destination_course_id
        )
        if hasattr(course_video, 'video_image'):
            VideoImage.create_or_update(
                course_video=destination_course_video,
                file_name=course_video.video_image.image.name
            )
Exemplo n.º 5
0
Arquivo: api.py Projeto: edx/edx-val
def copy_course_videos(source_course_id, destination_course_id):
    """
    Adds the destination_course_id to the videos taken from the source_course_id

    Args:
        source_course_id: The original course_id
        destination_course_id: The new course_id where the videos will be copied
    """
    if source_course_id == destination_course_id:
        return

    course_videos = CourseVideo.objects.select_related('video', 'video_image').filter(
        course_id=six.text_type(source_course_id)
    )

    for course_video in course_videos:
        destination_course_video, __ = CourseVideo.objects.get_or_create(
            video=course_video.video,
            course_id=destination_course_id
        )
        if hasattr(course_video, 'video_image'):
            VideoImage.create_or_update(
                course_video=destination_course_video,
                file_name=course_video.video_image.image.name
            )
Exemplo n.º 6
0
def update_video_image(edx_video_id, course_id, image_data, file_name):
    """
    Update video image for an existing video.

    NOTE: If `image_data` is None then `file_name` value will be used as it is, otherwise
    a new file name is constructed based on uuid and extension from `file_name` value.
    `image_data` will be None in case of course re-run and export.

    Arguments:
        image_data (InMemoryUploadedFile): Image data to be saved for a course video.

    Returns:
        course video image url

    Raises:
        Raises ValVideoNotFoundError if the CourseVideo cannot be retrieved.
    """
    try:
        course_video = CourseVideo.objects.select_related('video').get(
            course_id=course_id, video__edx_video_id=edx_video_id)
    except ObjectDoesNotExist:
        error_message = u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
            edx_video_id, course_id)
        raise ValVideoNotFoundError(error_message)

    video_image, _ = VideoImage.create_or_update(course_video, file_name,
                                                 image_data)
    return video_image.image_url()
Exemplo n.º 7
0
Arquivo: api.py Projeto: edx/edx-val
def update_video_image(edx_video_id, course_id, image_data, file_name):
    """
    Update video image for an existing video.

    NOTE: If `image_data` is None then `file_name` value will be used as it is, otherwise
    a new file name is constructed based on uuid and extension from `file_name` value.
    `image_data` will be None in case of course re-run and export.

    Arguments:
        image_data (InMemoryUploadedFile): Image data to be saved for a course video.

    Returns:
        course video image url

    Raises:
        Raises ValVideoNotFoundError if the CourseVideo cannot be retrieved.
    """
    try:
        course_video = CourseVideo.objects.select_related('video').get(
            course_id=course_id, video__edx_video_id=edx_video_id
        )
    except ObjectDoesNotExist:
        error_message = u'VAL: CourseVideo not found for edx_video_id: {0} and course_id: {1}'.format(
            edx_video_id,
            course_id
        )
        raise ValVideoNotFoundError(error_message)

    video_image, _ = VideoImage.create_or_update(course_video, file_name, image_data)
    return video_image.image_url()
Exemplo n.º 8
0
 def test_generated_images_when_no_image_exists(self):
     """
     Test that if generated_images of video image are updated when no previous
     manual upload exists, then first generated_image is set as the thumbnail.
     """
     video_image, _ = VideoImage.create_or_update(
         self.course_video, generated_images=self.generated_images)
     self.assertEqual(video_image.image, self.generated_images[0])
Exemplo n.º 9
0
    def post(self, request):
        """
        Update a course video image instance with auto generated image names.
        """
        attrs = ('course_id', 'edx_video_id', 'generated_images')
        missing = [attr for attr in attrs if attr not in request.data]
        if missing:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    'message':
                    u'{missing} must be specified to update a video image.'.
                    format(missing=' and '.join(missing))
                })

        course_id = request.data['course_id']
        edx_video_id = request.data['edx_video_id']
        generated_images = request.data['generated_images']

        try:
            validate_generated_images(generated_images, LIST_MAX_ITEMS)
        except Exception as e:  # pylint: disable=broad-except
            return Response(status=status.HTTP_400_BAD_REQUEST,
                            data={'message': str(e)})

        try:
            course_video = CourseVideo.objects.select_related(
                'video_image').get(course_id=str(course_id),
                                   video__edx_video_id=edx_video_id)
        except CourseVideo.DoesNotExist:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    'message':
                    u'CourseVideo not found for course_id: {course_id}'.format(
                        course_id=course_id)
                })

        try:
            VideoImage.create_or_update(course_video,
                                        generated_images=generated_images)
        except ValidationError as ex:
            return Response(status=status.HTTP_400_BAD_REQUEST,
                            data={'message': str(ex)})

        return Response()
Exemplo n.º 10
0
    def test_generated_images_when_image_exists(self):
        """
        Test that if generated_images of video image are updated when previous
        manual upload exists, then image field does not change.
        """
        manually_uploaded_img = 'manual-upload.jpeg'
        self.video_image.image = manually_uploaded_img
        self.video_image.save()

        video_image, _ = VideoImage.create_or_update(
            self.course_video, generated_images=self.generated_images)
        self.assertNotEqual(video_image.image, self.generated_images[0])
        self.assertEqual(video_image.image, manually_uploaded_img)
Exemplo n.º 11
0
Arquivo: views.py Projeto: edx/edx-val
    def post(self, request):
        """
        Update a course video image instance with auto generated image names.
        """
        attrs = ('course_id', 'edx_video_id', 'generated_images')
        missing = [attr for attr in attrs if attr not in request.data]
        if missing:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={
                    'message': u'{missing} must be specified to update a video image.'.format(
                        missing=' and '.join(missing)
                    )
                }
            )

        course_id = request.data['course_id']
        edx_video_id = request.data['edx_video_id']
        generated_images = request.data['generated_images']

        try:
            course_video = CourseVideo.objects.select_related('video_image').get(
                course_id=six.text_type(course_id), video__edx_video_id=edx_video_id
            )
        except CourseVideo.DoesNotExist:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={'message': u'CourseVideo not found for course_id: {course_id}'.format(course_id=course_id)}
            )

        try:
            VideoImage.create_or_update(course_video, generated_images=generated_images)
        except ValidationError as ex:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={'message': str(ex)}
            )

        return Response()
Exemplo n.º 12
0
    def create(self, validated_data):
        """
        Create the video and its nested resources.
        """
        courses = validated_data.pop("courses", [])
        encoded_videos = validated_data.pop("encoded_videos", [])

        video = Video.objects.create(**validated_data)

        EncodedVideo.objects.bulk_create(
            EncodedVideo(video=video, **video_data)
            for video_data in encoded_videos
        )

        # The CourseSerializer will already have converted the course data
        # to CourseVideo models, so we can just set the video and save.
        # Also create VideoImage objects if an image filename is present
        for course_video, image_name in courses:
            course_video.video = video
            course_video.save()
            if image_name:
                VideoImage.create_or_update(course_video, image_name)

        return video
Exemplo n.º 13
0
def import_from_xml(xml, edx_video_id, course_id=None):
    """
    Imports data from a video_asset element about the given video_id.

    If the edx_video_id already exists, then no changes are made. If an unknown
    profile is referenced by an encoded video, that encoding will be ignored.

    Arguments:
        xml (Element): An lxml video_asset element containing import data
        edx_video_id (str): val video id
        course_id (str): The ID of a course to associate the video with

    Raises:
        ValCannotCreateError: if there is an error importing the video
    """
    if xml.tag != 'video_asset':
        raise ValCannotCreateError('Invalid XML')

    # if edx_video_id does not exist then create video transcripts only
    if not edx_video_id:
        return create_transcript_objects(xml)

    # If video with edx_video_id already exists, associate it with the given course_id.
    try:
        video = Video.objects.get(edx_video_id=edx_video_id)
        logger.info(
            "edx_video_id '%s' present in course '%s' not imported because it exists in VAL.",
            edx_video_id,
            course_id,
        )
        if course_id:
            course_video, __ = CourseVideo.get_or_create_with_validation(
                video=video, course_id=course_id)

            image_file_name = xml.get('image', '').strip()
            if image_file_name:
                VideoImage.create_or_update(course_video, image_file_name)

        # import transcripts
        create_transcript_objects(xml)

        return
    except ValidationError as err:
        logger.exception(err.message)
        raise ValCannotCreateError(err.message_dict)
    except Video.DoesNotExist:
        pass

    # Video with edx_video_id did not exist, so create one from xml data.
    data = {
        'edx_video_id': edx_video_id,
        'client_video_id': xml.get('client_video_id'),
        'duration': xml.get('duration'),
        'status': 'imported',
        'encoded_videos': [],
        'courses': [{
            course_id: xml.get('image')
        }] if course_id else [],
    }
    for encoded_video_el in xml.iterfind('encoded_video'):
        profile_name = encoded_video_el.get('profile')
        try:
            Profile.objects.get(profile_name=profile_name)
        except Profile.DoesNotExist:
            logger.info(
                "Imported edx_video_id '%s' contains unknown profile '%s'.",
                edx_video_id, profile_name)
            continue
        data['encoded_videos'].append({
            'profile':
            profile_name,
            'url':
            encoded_video_el.get('url'),
            'file_size':
            encoded_video_el.get('file_size'),
            'bitrate':
            encoded_video_el.get('bitrate'),
        })
    create_video(data)
    create_transcript_objects(xml)
Exemplo n.º 14
0
def import_from_xml(xml, edx_video_id, resource_fs, static_dir, external_transcripts=None, course_id=None):
    """
    Imports data from a video_asset element about the given video_id.
    If the edx_video_id already exists, then no changes are made. If an unknown
    profile is referenced by an encoded video, that encoding will be ignored.

    Arguments:
        xml (Element): An lxml video_asset element containing import data
        edx_video_id (str): val video id
        resource_fs (OSFS): Import file system.
        static_dir (str): The Directory to retrieve transcript file.
        external_transcripts : A dict containing the list of names of the external transcripts.
        course_id: course id
            Example:
            {
                'en': ['The_Flash.srt', 'Harry_Potter.srt'],
                'es': ['Green_Arrow.srt']
            }
        course_id (str): The ID of a course to associate the video with

    Raises:
        ValCannotCreateError: if there is an error importing the video

    Returns:
        edx_video_id (str): val video id.
    """
    if external_transcripts is None:
        external_transcripts = {}

    if xml.tag != 'video_asset':
        raise ValCannotCreateError('Invalid XML')

    # If video with edx_video_id already exists, associate it with the given course_id.
    try:
        if not edx_video_id:
            raise Video.DoesNotExist

        video = Video.objects.get(edx_video_id=edx_video_id)
        logger.info(
            "edx_video_id '%s' present in course '%s' not imported because it exists in VAL.",
            edx_video_id,
            course_id,
        )

        # We don't want to link an existing video to course if its an external video.
        # External videos do not have any playback profiles associated, these are just to track video
        # transcripts for those video components who do not use edx hosted videos for playback.
        if course_id and video.status != EXTERNAL_VIDEO_STATUS:
            course_video, __ = CourseVideo.get_or_create_with_validation(video=video, course_id=course_id)

            image_file_name = xml.get('image', '').strip()
            if image_file_name:
                VideoImage.create_or_update(course_video, image_file_name)

        # Make sure transcripts are imported when video exists
        create_transcript_objects(
            xml,
            edx_video_id,
            resource_fs,
            static_dir,
            external_transcripts
        )

        return edx_video_id
    except ValidationError as err:
        logger.exception(xml)
        raise ValCannotCreateError(err.message_dict) from err
    except Video.DoesNotExist:
        pass

    if edx_video_id:
        # Video with edx_video_id did not exist, so create one from xml data.
        data = {
            'edx_video_id': edx_video_id,
            'client_video_id': xml.get('client_video_id'),
            'duration': xml.get('duration'),
            'status': 'imported',
            'encoded_videos': [],
            'courses': [{course_id: xml.get('image')}] if course_id else [],
        }
        for encoded_video_el in xml.iterfind('encoded_video'):
            profile_name = encoded_video_el.get('profile')
            try:
                Profile.objects.get(profile_name=profile_name)
            except Profile.DoesNotExist:
                logger.info(
                    "Imported edx_video_id '%s' contains unknown profile '%s'.",
                    edx_video_id,
                    profile_name
                )
                continue
            data['encoded_videos'].append({
                'profile': profile_name,
                'url': encoded_video_el.get('url'),
                'file_size': encoded_video_el.get('file_size'),
                'bitrate': encoded_video_el.get('bitrate'),
            })

        if not data['encoded_videos']:
            # Video's status does not get included in video xml at the time of export. So, at this point,
            # we cannot tell from xml that whether a video had an external status. But if encoded videos
            # are not set, the chances are, the video was an external one, in which case, we will not link
            # it to the course(s). Even if the video wasn't an external one and it is having 0 encodes in
            # xml, it does not have a side effect if not linked to a course, since the video was already
            # non-playable.
            data['status'] = EXTERNAL_VIDEO_STATUS
            data['courses'] = []

        # Create external video if no edx_video_id.
        edx_video_id = create_video(data)
    else:
        edx_video_id = create_external_video('External Video')

    create_transcript_objects(xml, edx_video_id, resource_fs, static_dir, external_transcripts)
    return edx_video_id
Exemplo n.º 15
0
Arquivo: api.py Projeto: edx/edx-val
def import_from_xml(xml, edx_video_id, resource_fs, static_dir, external_transcripts=dict(), course_id=None):
    """
    Imports data from a video_asset element about the given video_id.

    If the edx_video_id already exists, then no changes are made. If an unknown
    profile is referenced by an encoded video, that encoding will be ignored.

    Arguments:
        xml (Element): An lxml video_asset element containing import data
        edx_video_id (str): val video id
        resource_fs (OSFS): Import file system.
        static_dir (str): The Directory to retrieve transcript file.
        external_transcripts (dict): A dict containing the list of names of the external transcripts.
            Example:
            {
                'en': ['The_Flash.srt', 'Harry_Potter.srt'],
                'es': ['Green_Arrow.srt']
            }
        course_id (str): The ID of a course to associate the video with

    Raises:
        ValCannotCreateError: if there is an error importing the video

    Returns:
        edx_video_id (str): val video id.
    """
    if xml.tag != 'video_asset':
        raise ValCannotCreateError('Invalid XML')

    # If video with edx_video_id already exists, associate it with the given course_id.
    try:
        if not edx_video_id:
            raise Video.DoesNotExist

        video = Video.objects.get(edx_video_id=edx_video_id)
        logger.info(
            "edx_video_id '%s' present in course '%s' not imported because it exists in VAL.",
            edx_video_id,
            course_id,
        )

        # We don't want to link an existing video to course if its an external video.
        # External videos do not have any playback profiles associated, these are just to track video
        # transcripts for those video components who do not use edx hosted videos for playback.
        if course_id and video.status != EXTERNAL_VIDEO_STATUS:
            course_video, __ = CourseVideo.get_or_create_with_validation(video=video, course_id=course_id)

            image_file_name = xml.get('image', '').strip()
            if image_file_name:
                VideoImage.create_or_update(course_video, image_file_name)

        return edx_video_id
    except ValidationError as err:
        logger.exception(err.message)
        raise ValCannotCreateError(err.message_dict)
    except Video.DoesNotExist:
        pass

    if edx_video_id:
        # Video with edx_video_id did not exist, so create one from xml data.
        data = {
            'edx_video_id': edx_video_id,
            'client_video_id': xml.get('client_video_id'),
            'duration': xml.get('duration'),
            'status': 'imported',
            'encoded_videos': [],
            'courses': [{course_id: xml.get('image')}] if course_id else [],
        }
        for encoded_video_el in xml.iterfind('encoded_video'):
            profile_name = encoded_video_el.get('profile')
            try:
                Profile.objects.get(profile_name=profile_name)
            except Profile.DoesNotExist:
                logger.info(
                    "Imported edx_video_id '%s' contains unknown profile '%s'.",
                    edx_video_id,
                    profile_name
                )
                continue
            data['encoded_videos'].append({
                'profile': profile_name,
                'url': encoded_video_el.get('url'),
                'file_size': encoded_video_el.get('file_size'),
                'bitrate': encoded_video_el.get('bitrate'),
            })

        if not data['encoded_videos']:
            # Video's status does not get included in video xml at the time of export. So, at this point,
            # we cannot tell from xml that whether a video had an external status. But if encoded videos
            # are not set, the chances are, the video was an external one, in which case, we will not link
            # it to the course(s). Even if the video wasn't an external one and it is having 0 encodes in
            # xml, it does not have a side effect if not linked to a course, since the video was already
            # non-playable.
            data['status'] = EXTERNAL_VIDEO_STATUS
            data['courses'] = []

        # Create external video if no edx_video_id.
        edx_video_id = create_video(data)
    else:
        edx_video_id = create_external_video('External Video')

    create_transcript_objects(xml, edx_video_id, resource_fs, static_dir, external_transcripts)
    return edx_video_id