Django Rest Framework:How 计算视频持续时间的百分比?

Django Rest Framework:How to calculate percentage of video duration?

在我的项目中,我有一个视频部分,我想在其中计算用户观看时间的百分比。通过下方URL可以查看视频详情

URL : video/video_id 输出:

"video": {
                "id": "84e7288c-dc09-44aa-850c-08546a98ffde",
                "deleted": null,
                "datetime_created": "02/04/2022 06:56 AM",
                "datetime_updated": "02/04/2022 06:56 AM",
                "video_name": "video name3",
                "description": "description about video",
                "duration": "00:33:20",
                "create_date": "02/04/2022 06:56 AM",
                "video_type": "micro",
                "file_url": "https://vimeo.com/216763352",
                "general_status": "high",
                "review_status": "draft",
                "video_number": "VD6129",
                "created_by": null
            },

"duration": "00:33:20" 是视频的总时长。 如何计算用户正在观看的视频时间的百分比,如果以总秒数传递时间

{
    "time":200
}

好吧,我最近做了类似的事情,我的任务是计算视频观看时间并相应地给予积分。我还被要求不要给出观看视频时间的百分比和分数。我使用合并算法来合并重叠的间隔。您可以根据您的任务要求自定义代码,因为您可能不会像我那样被要求给用户积分并进行独特的 100% 准确计算,这是我所做的:

models.py

from django.db import models
from django.apps import apps
from django.db.models import Sum, F, Subquery
from django.db.models.functions import Coalesce
from django.utils.translation import ugettext_lazy as _
from django.dispatch import receiver
from django.db.models.signals import post_save

from common.models import BaseUser
from helpers.models import BaseModel


class LessonTypeChoices(models.TextChoices):
    video = "video", _("Video")
    task = "task", _("Task")
    exam = "exam", _("Exam")
    book = "book", _("Book")
    audiobook = "audiobook", _("Audio book")


class Lesson(BaseModel):
    course = models.ForeignKey(
        "courses.Course",
        on_delete=models.CASCADE,
        related_name="lessons",
        verbose_name=_("course"),
    )
    type = models.CharField(_("type"), max_length=32, choices=LessonTypeChoices.choices)
    title = models.CharField(_("title"), max_length=256)
    description = models.TextField(_("description"))
    points = models.IntegerField(_("points"), default=0)
    order = models.IntegerField(_("order"), default=0)

    def __str__(self):
        return f"{self.title} - {self.type}"

    class Meta:
        db_table = "lesson"
        verbose_name = _("lesson")
        verbose_name_plural = _("lessons")


@receiver(post_save, sender=Lesson)
def add_course_point(sender, instance, created, **kwargs):
    """
    Increment course points by lesson points when lesson is created
    """
    Course = apps.get_model("courses.Course")
    Course.objects.filter(id=instance.course_id).update(
        points=Subquery(
            Course.objects.filter(id=instance.course_id)
            .annotate(sum_points=Sum("lessons__points"))
            .values("sum_points")
        )
    )

class LessonProgress(BaseModel):
    lesson = models.ForeignKey(
        Lesson,
        on_delete=models.CASCADE,
        related_name="lesson_progress",
        verbose_name=_("lesson"),
    )
    user = models.ForeignKey(
        BaseUser,
        on_delete=models.CASCADE,
        related_name="lesson_progress",
        verbose_name=_("user"),
    )
    type = models.CharField(_("type"), max_length=32, choices=LessonTypeChoices.choices)
    percentage = models.FloatField(_("progress percentage"), default=0)
    points = models.FloatField(_("points"), default=0)
    is_locked = models.BooleanField(_("lesson is locked"), default=True)
    is_completed = models.BooleanField(_("lesson is completed"), default=False)
    last_progress = models.IntegerField(_("last progress"), default=0)

    class Meta:
        unique_together = ("lesson", "user")
        db_table = "lesson_progress"
        verbose_name = _("lesson progress")
        verbose_name_plural = _("lesson progress")

    def set_locked(self, instance, user):
        """
        Update lessons' locked status
        """
        percentage = self.get_percentage(instance, user)

        for index, lesson in enumerate(instance.lessons.all().order_by("order")):
            if index in [0, 1] or percentage / ((index + 1) - 2) >= 80:
                lesson.lesson_progress.update(is_locked=False)
            else:
                lesson.lesson_progress.update(is_locked=True)

    @staticmethod
    def get_percentage(instance, user):
        """
        Calculate average percentage of completed lessons
        """
        percentage = (
            instance.lessons.all()
            .aggregate(
                percentage=Coalesce(
                    Sum(
                        "lesson_progress__percentage",
                        filter=models.Q(lesson_progress__user=user),
                    ),
                    0,
                )
            )
            .get("percentage")
        )
        return percentage

    @staticmethod
    def is_complete(percentage):
        """
        decide whether lesson is completed or not
        """
        if percentage >= 90:
            return True
        return False

    @staticmethod
    def calculate_percentage(length, progress):
        """
        Calculate the watched video or submitted exam answer percentage
        """
        return progress / length * 100

    @staticmethod
    def calculate_points(
        instance,
        percentage,
    ):
        """
        Calculate earned points by lesson progress percentage
        """
        return instance.points / 100 * percentage

    @staticmethod
    def user_points(user, points, is_expired):
        """
        Add points to user
        """
        if not is_expired:
            user.score += points
            user.save()

    @staticmethod
    def task_progress_points(instance, percentage, is_completed, points, is_expired):
        """
        Update task lesson progress -> percentage, is_completed, points
        """
        instance.percentage += percentage
        instance.is_completed = is_completed

        if not is_expired:
            instance.points += points

        instance.save()

    @staticmethod
    def exam_video_progress_points(
        instance, percentage, last_progress, is_completed, points, is_expired
    ):
        """
        Update exam video lesson progress -> percentage, last_progress, is_completed, points
        """
        instance.percentage += percentage
        instance.last_progress = last_progress
        instance.is_completed = is_completed

        if not is_expired:
            instance.points += points

        instance.save()

    @staticmethod
    def merge(intervals, progress_before):
        """
        merge queryset with each other, remove overlapping intervals and create
        """
        bulk_list = []

        for progress in intervals:
            # merge overlapping intervals
            if bulk_list and progress.start_progress <= bulk_list[-1].end_progress:
                bulk_list[-1].end_progress = max(
                    bulk_list[-1].end_progress, progress.end_progress
                )
                bulk_list[-1].progress = (
                    max(bulk_list[-1].end_progress, progress.end_progress)
                    - bulk_list[0].start_progress
                )
                bulk_list[-1].video_length = max(
                    bulk_list[-1].video_length, progress.video_length
                )

            else:
                # add merged interval object to bulk_list
                bulk_list.append(
                    LessonVideoProgress(
                        lesson_progress_id=progress.lesson_progress_id,
                        video_length=progress.video_length,
                        start_progress=progress.start_progress,
                        end_progress=progress.end_progress,
                        progress=progress.end_progress - progress.start_progress,
                    )
                )

        # delete all interval objects
        intervals.delete()

        # create intervals from bulk_list
        LessonVideoProgress.objects.bulk_create(bulk_list)

        # calculate the duration seconds of new intervals
        progress_after = intervals.aggregate(progress_sum=Sum("progress")).get(
            "progress_sum"
        )

        # get the watched duration seconds -> subtracting old progress seconds by new progress seconds
        return float(progress_after - progress_before)

    @staticmethod
    def progress_before_merge(queryset):
        # calculate the progress duration seconds sum before creating new video progress instance
        return queryset.aggregate(progress_sum=Coalesce(Sum("progress"), 0)).get(
            "progress_sum"
        )

    @staticmethod
    def create_lesson_video_progress(
        progress_id, video_length, start_progress, end_progress
    ):
        # create new video progress instance
        LessonVideoProgress.objects.create(
            lesson_progress_id=progress_id,
            video_length=video_length,
            start_progress=start_progress,
            end_progress=end_progress,
        )

    @staticmethod
    def round_percentage(percentage, progress_percentage):
        # round the given percentages to 100 in case it exceeds it
        return percentage - ((progress_percentage + percentage) - 100)


@receiver(post_save, sender=LessonProgress)
def run_lessons(sender, instance, **kwargs):
    if instance.pk:
        instance.set_locked(instance.lesson.course, instance.user)


class LessonVideoProgress(BaseModel):
    lesson_progress = models.ForeignKey(
        LessonProgress, on_delete=models.CASCADE, related_name="lesson_video_progress"
    )
    video_length = models.DecimalField(
        _("video length"), max_digits=19, decimal_places=3
    )
    progress = models.DecimalField(
        _("progress duration"), default=0, max_digits=19, decimal_places=3
    )
    start_progress = models.DecimalField(
        _("start progress"), max_digits=19, decimal_places=3
    )
    end_progress = models.DecimalField(
        _("end progress"), max_digits=19, decimal_places=3
    )

    class Meta:
        db_table = "lesson_video_progress"
        verbose_name = _("lesson video progress")
        verbose_name_plural = _("lesson video progress")

    def __str__(self):
        return f"{self.progress}"

views.py

class LessonVideoExamSubmitView(generics.GenericAPIView):
    queryset = LessonProgress.objects.all()
    serializer_class = serializers.LessonVideoExamSubmitSerializer

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(
            data=request.data, context={"request": request}
        )
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

serializers.py

class LessonVideoExamSubmitSerializer(serializers.Serializer):
    lesson_id = serializers.IntegerField()
    video_length = serializers.FloatField()
    start_progress = serializers.FloatField()
    end_progress = serializers.FloatField()
    is_expired = serializers.BooleanField(default=False)

    def create(self, validated_data):
        # get lesson progress for given lesson id
        progress = LessonProgress.objects.get(
            lesson_id=validated_data.get("lesson_id"), user=self.context["request"].user
        )

        # get the current video progress before merging with other progresses
        progress_before_merge = progress.progress_before_merge(
            progress.lesson_video_progress
        )

    # create new video progress for given intervals
    progress.create_lesson_video_progress(
        progress.id,
        validated_data.get("video_length"),
        validated_data.get("start_progress"),
        validated_data.get("end_progress"),
    )

    # merge created video progress objects with existing objects in the database
    progress_merge = progress.merge(
        progress.lesson_video_progress.all().order_by("start_progress"),
        progress_before_merge,
    )

    # pass video length and merged objects duration in seconds and get watched video percentage
    percentage = progress.calculate_percentage(
        validated_data.get("video_length"), progress_merge
    )

    if progress.percentage + percentage >= 100:
        """
        add progress percentage and calculated percentage, round the sum to 100
        """
        percentage = progress.round_percentage(percentage, progress.percentage)

    # get points to the lesson by passing the percentage
    points = progress.calculate_points(progress.lesson, percentage)

    # add points to user
    progress.user_points(progress.user, points, validated_data.get("is_expired"))

    # get completed status by sum of progress percentages
    is_completed = progress.is_complete(progress.percentage + percentage)

    # update lesson progress object
    progress.exam_video_progress_points(
        progress,
        percentage,
        validated_data.get("end_progress"),
        is_completed,
        points,
        validated_data.get("is_expired"),
    )

    return validated_data

注意,您可以为自己的视频计算逻辑优化和删除不必要的部分。这也可以用于音频计算逻辑。我平时不分享源代码,只是因为我真的想帮忙,所以我做了,干杯)