Commit e5b5ca6d by muzaffaryousaf

# This is a combination of 2 commits.

# The first commit's message is:

# This is a combination of 4 commits.
# The first commit's message is:
Transcript bucket settings.

# The 2nd commit message will be skipped:

#	Transcript file field to use django storage.

# The 3rd commit message will be skipped:

#	example method to get_or_create the transcript url.

# The 4th commit message will be skipped:

#	Adding file_name to the transcript method.

# The 2nd commit message will be skipped:

#	Transcript bucket settings.
parent ed6a1588
...@@ -69,3 +69,5 @@ venv/ ...@@ -69,3 +69,5 @@ venv/
venvs/ venvs/
video-images/ video-images/
video-transcripts/
...@@ -198,7 +198,7 @@ def create_video_transcript(video_id, language, transcript_url, transcript_forma ...@@ -198,7 +198,7 @@ def create_video_transcript(video_id, language, transcript_url, transcript_forma
transcript = Transcript.objects.create( transcript = Transcript.objects.create(
video_id=video_id, video_id=video_id,
language=language, language=language,
transcript_url=transcript_url, transcript=transcript_url,
fmt=transcript_format, fmt=transcript_format,
provider=provider, provider=provider,
) )
...@@ -222,7 +222,7 @@ def update_video_transcript(video_id, language, transcript_url, transcript_forma ...@@ -222,7 +222,7 @@ def update_video_transcript(video_id, language, transcript_url, transcript_forma
""" """
transcript = Transcript.objects.get(video_id=video_id, language=language) transcript = Transcript.objects.get(video_id=video_id, language=language)
transcript.language = language transcript.language = language
transcript.transcript_url = transcript_url transcript.transcript = transcript_url
transcript.fmt = transcript_format transcript.fmt = transcript_format
transcript.provider = provider transcript.provider = provider
transcript.save() transcript.save()
...@@ -230,6 +230,11 @@ def update_video_transcript(video_id, language, transcript_url, transcript_forma ...@@ -230,6 +230,11 @@ def update_video_transcript(video_id, language, transcript_url, transcript_forma
return TranscriptSerializer(transcript).data return TranscriptSerializer(transcript).data
def create_update_video_transcript(video_id, language, file_data, file_name=None):
video_transcript, _ = Transcript.create_or_update(video_id=video_id,language=language, file_data=file_data,file_name=file_name)
return video_transcript.transcript_url()
def get_course_video_image_url(course_id, edx_video_id): def get_course_video_image_url(course_id, edx_video_id):
""" """
Returns course video image url or None if no image found Returns course video image url or None if no image found
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
import edxval.models
class Migration(migrations.Migration):
dependencies = [
('edxval', '0005_videoimage'),
]
operations = [
migrations.CreateModel(
name='Transcript',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('video_id', models.CharField(max_length=255)),
('transcript', edxval.models.CustomizableFileField(null=True, blank=True)),
('language', models.CharField(max_length=8, db_index=True)),
('provider', models.CharField(default=b'Custom', max_length=30, choices=[(b'Custom', b'Custom'), (b'3PlayMedia', b'3PlayMedia'), (b'Cielo24', b'Cielo24')])),
('fmt', models.CharField(db_index=True, max_length=20, choices=[(b'srt', b'SubRip'), (b'sjson', b'SRT JSON')])),
],
),
migrations.RemoveField(
model_name='subtitle',
name='video',
),
migrations.DeleteModel(
name='Subtitle',
),
migrations.AlterUniqueTogether(
name='transcript',
unique_together=set([('video_id', 'language')]),
),
]
...@@ -26,7 +26,7 @@ from django.utils.six import python_2_unicode_compatible ...@@ -26,7 +26,7 @@ from django.utils.six import python_2_unicode_compatible
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from edxval.utils import video_image_path, get_video_image_storage from edxval.utils import video_image_path, get_video_image_storage, video_transcript_path, get_video_transcript_storage
logger = logging.getLogger(__name__) # pylint: disable=C0103 logger = logging.getLogger(__name__) # pylint: disable=C0103
...@@ -357,6 +357,35 @@ class TranscriptFormat(object): ...@@ -357,6 +357,35 @@ class TranscriptFormat(object):
) )
class CustomizableFileField(models.FileField):
"""
Subclass of FileField that allows custom settings to not
be serialized (hard-coded) in migrations. Otherwise,
migrations include optional settings for storage (such as
the storage class and bucket name); we don't want to
create new migration files for each configuration change.
"""
def __init__(self, *args, **kwargs):
kwargs.update(dict(
upload_to=video_transcript_path,
storage=get_video_transcript_storage(),
max_length=500, # allocate enough for filepath
blank=True,
null=True
))
super(CustomizableFileField, self).__init__(*args, **kwargs)
def deconstruct(self):
"""
Override base class method.
"""
name, path, args, kwargs = super(CustomizableFileField, self).deconstruct()
del kwargs['upload_to']
del kwargs['storage']
del kwargs['max_length']
return name, path, args, kwargs
class Transcript(TimeStampedModel): class Transcript(TimeStampedModel):
""" """
Transcript for a video Transcript for a video
...@@ -371,7 +400,7 @@ class Transcript(TimeStampedModel): ...@@ -371,7 +400,7 @@ class Transcript(TimeStampedModel):
""" """
# It can be an edx_video_id or an external video id (e.g. in case of external URLs - YT/MP4/WEBM etc.) # It can be an edx_video_id or an external video id (e.g. in case of external URLs - YT/MP4/WEBM etc.)
video_id = models.CharField(max_length=255) video_id = models.CharField(max_length=255)
transcript_url = models.TextField(null=True, blank=True) transcript = CustomizableFileField()
language = models.CharField(max_length=8, db_index=True) language = models.CharField(max_length=8, db_index=True)
provider = models.CharField( provider = models.CharField(
max_length=30, max_length=30,
...@@ -389,6 +418,31 @@ class Transcript(TimeStampedModel): ...@@ -389,6 +418,31 @@ class Transcript(TimeStampedModel):
def __str__(self): def __str__(self):
return '{lang} Transcript for {video}'.format(lang=self.language, video=self.video_id) return '{lang} Transcript for {video}'.format(lang=self.language, video=self.video_id)
def transcript_url(self):
"""
Return transcript url for a course video transcript.
"""
storage = get_video_transcript_storage()
return storage.url(self.transcript.name)
@classmethod
def create_or_update(cls, video_id, language, file_name=None, file_data=None):
"""
Create Transcript object.
"""
video_transcript, created = cls.objects.get_or_create(video_id=video_id, language=language, fmt=TranscriptFormat.SJSON)
with closing(file_data) as transcript_file_data:
file_name = '{uuid}{ext}'.format(uuid=uuid4().hex, ext=os.path.splitext(file_name)[1])
try:
video_transcript.transcript.save(file_name, transcript_file_data)
except Exception: # pylint: disable=broad-except
logger.exception('VAL: Video Transcript save failed to storage for video_id [%s]', video_id)
raise
return video_transcript, created
@receiver(models.signals.post_save, sender=Video) @receiver(models.signals.post_save, sender=Video)
def video_status_update_callback(sender, **kwargs): # pylint: disable=unused-argument def video_status_update_callback(sender, **kwargs): # pylint: disable=unused-argument
......
...@@ -54,10 +54,18 @@ class TranscriptSerializer(serializers.ModelSerializer): ...@@ -54,10 +54,18 @@ class TranscriptSerializer(serializers.ModelSerializer):
""" """
Serializer for Transcript objects Serializer for Transcript objects
""" """
transcript_url = serializers.SerializerMethodField()
class Meta: # pylint: disable=C1001, C0111 class Meta: # pylint: disable=C1001, C0111
model = Transcript model = Transcript
lookup_field = 'video_id' lookup_field = 'video_id'
fields = ('video_id', 'transcript_url', 'language', 'provider', 'fmt') fields = ('video_id', 'language', 'provider', 'fmt', 'transcript_url')
def get_transcript_url(self, obj):
"""
Return relative url for the object
"""
return obj.transcript_url
class CourseSerializer(serializers.RelatedField): class CourseSerializer(serializers.RelatedField):
......
...@@ -190,3 +190,13 @@ VIDEO_IMAGE_SETTINGS = dict( ...@@ -190,3 +190,13 @@ VIDEO_IMAGE_SETTINGS = dict(
VIDEO_IMAGE_MIN_BYTES=100, VIDEO_IMAGE_MIN_BYTES=100,
DIRECTORY_PREFIX='video-images/', DIRECTORY_PREFIX='video-images/',
) )
VIDEO_TRANSCRIPTS_SETTINGS = dict(
# Backend storage
# STORAGE_CLASS='storages.backends.s3boto.S3BotoStorage',
# STORAGE_KWARGS=dict(bucket='video-transcripts-bucket'),
# If you are changing prefix value then update the .gitignore accordingly
# so that transcripts created during tests due to upload should be ignored
VIDEO_TRANSCRIPTS_MAX_BYTES=3145728,
DIRECTORY_PREFIX='video-transcripts/',
)
...@@ -29,3 +29,28 @@ def get_video_image_storage(): ...@@ -29,3 +29,28 @@ def get_video_image_storage():
# during edx-platform loading this method gets called but settings are not ready yet # during edx-platform loading this method gets called but settings are not ready yet
# so in that case we will return default(FileSystemStorage) storage class instance # so in that case we will return default(FileSystemStorage) storage class instance
return get_storage_class()() return get_storage_class()()
def video_transcript_path(video_transcript_instance, filename): # pylint:disable=unused-argument
"""
Returns video transcript path.
Arguments:
video_transcript_instance (VideoImage): This is passed automatically by models.CustomizableImageField
filename (str): name of image file
"""
return u'{}{}'.format(settings.VIDEO_TRANSCRIPTS_SETTINGS.get('DIRECTORY_PREFIX', ''), filename)
def get_video_transcript_storage():
"""
Return the configured django storage backend.
"""
if hasattr(settings, 'VIDEO_TRANSCRIPTS_SETTINGS'):
return get_storage_class(
settings.VIDEO_TRANSCRIPTS_SETTINGS.get('STORAGE_CLASS'),
)(**settings.VIDEO_TRANSCRIPTS_SETTINGS.get('STORAGE_KWARGS', {}))
else:
# during edx-platform loading this method gets called but settings are not ready yet
# so in that case we will return default(FileSystemStorage) storage class instance
return get_storage_class()()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment