add serializers for video
This commit is contained in:
parent
5772ab3e68
commit
88132fef3c
|
|
@ -201,7 +201,7 @@ class YoutubePlaylist(YouTubeItem):
|
|||
|
||||
current_idx = all_entries.index(current[0])
|
||||
if current_idx == 0:
|
||||
previous_item = False
|
||||
previous_item = None
|
||||
else:
|
||||
previous_item = all_entries[current_idx - 1]
|
||||
prev_id = previous_item["youtube_id"]
|
||||
|
|
@ -209,7 +209,7 @@ class YoutubePlaylist(YouTubeItem):
|
|||
previous_item["vid_thumb"] = f"{cache_root}/{prev_thumb_path}"
|
||||
|
||||
if current_idx == len(all_entries) - 1:
|
||||
next_item = False
|
||||
next_item = None
|
||||
else:
|
||||
next_item = all_entries[current_idx + 1]
|
||||
next_id = next_item["youtube_id"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
"""video serializers"""
|
||||
|
||||
# pylint: disable=abstract-method
|
||||
|
||||
from channel.serializers import ChannelSerializer
|
||||
from common.serializers import PaginationSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class PlayerSerializer(serializers.Serializer):
|
||||
"""serialize player"""
|
||||
|
||||
watched = serializers.BooleanField()
|
||||
duration = serializers.IntegerField()
|
||||
duration_str = serializers.CharField()
|
||||
progress = serializers.FloatField(required=False)
|
||||
position = serializers.FloatField(required=False)
|
||||
|
||||
|
||||
class SponsorBlockSegmentSerializer(serializers.Serializer):
|
||||
"""serialize sponsorblock segment"""
|
||||
|
||||
actionType = serializers.CharField()
|
||||
videoDuration = serializers.FloatField()
|
||||
segment = serializers.ListField(child=serializers.FloatField())
|
||||
votes = serializers.IntegerField()
|
||||
category = serializers.CharField()
|
||||
UUID = serializers.CharField()
|
||||
locked = serializers.IntegerField()
|
||||
|
||||
|
||||
class SponsorBlockSerializer(serializers.Serializer):
|
||||
"""serialize sponsorblock"""
|
||||
|
||||
is_enabled = serializers.BooleanField()
|
||||
last_refresh = serializers.IntegerField()
|
||||
has_unlocked = serializers.BooleanField(required=False)
|
||||
segments = SponsorBlockSegmentSerializer(many=True)
|
||||
|
||||
|
||||
class StatsSerializer(serializers.Serializer):
|
||||
"""serialize stats"""
|
||||
|
||||
like_count = serializers.IntegerField(required=False)
|
||||
average_rating = serializers.FloatField(required=False)
|
||||
view_count = serializers.IntegerField(required=False)
|
||||
dislike_count = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
class StreamItemSerializer(serializers.Serializer):
|
||||
"""serialize stream item"""
|
||||
|
||||
index = serializers.IntegerField()
|
||||
codec = serializers.CharField()
|
||||
bitrate = serializers.IntegerField()
|
||||
type = serializers.ChoiceField(choices=["video", "audio"])
|
||||
width = serializers.IntegerField(required=False)
|
||||
height = serializers.IntegerField(required=False)
|
||||
|
||||
|
||||
class SubtitleItemSerializer(serializers.Serializer):
|
||||
"""serialize subtitle item"""
|
||||
|
||||
ext = serializers.ChoiceField(choices=["json3"])
|
||||
name = serializers.CharField()
|
||||
source = serializers.ChoiceField(choices=["user", "auto"])
|
||||
lang = serializers.CharField()
|
||||
media_url = serializers.CharField()
|
||||
url = serializers.URLField()
|
||||
|
||||
|
||||
class VideoSerializer(serializers.Serializer):
|
||||
"""serialize video item"""
|
||||
|
||||
active = serializers.BooleanField()
|
||||
category = serializers.ListField(child=serializers.CharField())
|
||||
channel = ChannelSerializer()
|
||||
comment_count = serializers.IntegerField(allow_null=True)
|
||||
date_downloaded = serializers.IntegerField()
|
||||
description = serializers.CharField()
|
||||
media_size = serializers.IntegerField()
|
||||
media_url = serializers.CharField()
|
||||
player = PlayerSerializer()
|
||||
playlist = serializers.ListField(
|
||||
child=serializers.CharField(), allow_null=True
|
||||
)
|
||||
published = serializers.CharField()
|
||||
sponsorblock = SponsorBlockSerializer(allow_null=True)
|
||||
stats = StatsSerializer()
|
||||
streams = StreamItemSerializer(many=True)
|
||||
subtitles = SubtitleItemSerializer(many=True)
|
||||
tags = serializers.ListField(child=serializers.CharField())
|
||||
title = serializers.CharField()
|
||||
vid_last_refresh = serializers.CharField()
|
||||
vid_thumb_url = serializers.CharField()
|
||||
vid_type = serializers.ChoiceField(choices=["video", "stream", "short"])
|
||||
youtube_id = serializers.CharField()
|
||||
_index = serializers.CharField(required=False)
|
||||
_score = serializers.FloatField(required=False)
|
||||
|
||||
|
||||
class VideoListSerializer(serializers.Serializer):
|
||||
"""serialize video list"""
|
||||
|
||||
data = VideoSerializer(many=True)
|
||||
paginate = PaginationSerializer()
|
||||
|
||||
|
||||
class VideoListQuerySerializer(serializers.Serializer):
|
||||
"""serialize query for video list"""
|
||||
|
||||
playlist = serializers.CharField(required=False)
|
||||
channel = serializers.CharField(required=False)
|
||||
watch = serializers.ChoiceField(
|
||||
choices=["watched", "unwatched", "continue"], required=False
|
||||
)
|
||||
sort = serializers.ChoiceField(
|
||||
choices=[
|
||||
"published",
|
||||
"downloaded",
|
||||
"views",
|
||||
"likes",
|
||||
"duration",
|
||||
"filesize",
|
||||
],
|
||||
required=False,
|
||||
)
|
||||
order = serializers.ChoiceField(choices=["asc", "desc"], required=False)
|
||||
type = serializers.ChoiceField(
|
||||
choices=["video", "stream", "short"], required=False
|
||||
)
|
||||
|
||||
|
||||
class CommentThreadItemSerializer(serializers.Serializer):
|
||||
"""serialize comment thread item"""
|
||||
|
||||
comment_id = serializers.CharField()
|
||||
comment_text = serializers.CharField()
|
||||
comment_timestamp = serializers.IntegerField()
|
||||
comment_time_text = serializers.CharField()
|
||||
comment_likecount = serializers.IntegerField()
|
||||
comment_is_favorited = serializers.BooleanField()
|
||||
comment_author = serializers.CharField()
|
||||
comment_author_id = serializers.CharField()
|
||||
comment_author_thumbnail = serializers.URLField()
|
||||
comment_author_is_uploader = serializers.BooleanField()
|
||||
comment_parent = serializers.CharField()
|
||||
|
||||
|
||||
class CommentItemSerializer(serializers.Serializer):
|
||||
"""serialize comment item"""
|
||||
|
||||
comment_id = serializers.CharField()
|
||||
comment_text = serializers.CharField()
|
||||
comment_timestamp = serializers.IntegerField()
|
||||
comment_time_text = serializers.CharField()
|
||||
comment_likecount = serializers.IntegerField()
|
||||
comment_is_favorited = serializers.BooleanField()
|
||||
comment_author = serializers.CharField()
|
||||
comment_author_id = serializers.CharField()
|
||||
comment_author_thumbnail = serializers.URLField()
|
||||
comment_author_is_uploader = serializers.BooleanField()
|
||||
comment_parent = serializers.CharField()
|
||||
comment_replies = CommentThreadItemSerializer(many=True)
|
||||
|
||||
|
||||
class PlaylistNavMetaSerializer(serializers.Serializer):
|
||||
"""serialize playlist nav meta"""
|
||||
|
||||
current_idx = serializers.IntegerField()
|
||||
playlist_id = serializers.CharField()
|
||||
playlist_name = serializers.CharField()
|
||||
playlist_channel = serializers.CharField()
|
||||
|
||||
|
||||
class PlaylistNavVideoSerializer(serializers.Serializer):
|
||||
"""serialize video item on playlist nav"""
|
||||
|
||||
youtube_id = serializers.CharField()
|
||||
title = serializers.CharField()
|
||||
uploader = serializers.CharField()
|
||||
idx = serializers.IntegerField()
|
||||
downloaded = serializers.BooleanField()
|
||||
vid_thumb = serializers.CharField()
|
||||
|
||||
|
||||
class PlaylistNavItemSerializer(serializers.Serializer):
|
||||
"""serialize nav on playlist"""
|
||||
|
||||
playlist_meta = PlaylistNavMetaSerializer()
|
||||
playlist_previous = PlaylistNavVideoSerializer(allow_null=True)
|
||||
playlist_next = PlaylistNavVideoSerializer(allow_null=True)
|
||||
|
||||
|
||||
class VideoProgressUpdateSerializer(serializers.Serializer):
|
||||
"""serialize progress update data"""
|
||||
|
||||
position = serializers.FloatField(default=0)
|
||||
|
|
@ -27,20 +27,20 @@ class QueryBuilder:
|
|||
must_list = []
|
||||
channel = self.request_params.get("channel")
|
||||
if channel:
|
||||
must_list.append({"match": {"channel.channel_id": channel[0]}})
|
||||
must_list.append({"match": {"channel.channel_id": channel}})
|
||||
|
||||
playlist = self.request_params.get("playlist")
|
||||
if playlist:
|
||||
must_list.append({"match": {"playlist.keyword": playlist[0]}})
|
||||
must_list.append({"match": {"playlist.keyword": playlist}})
|
||||
|
||||
watch = self.request_params.get("watch")
|
||||
if watch:
|
||||
watch_must_list = self.parse_watch(watch[0])
|
||||
watch_must_list = self.parse_watch(watch)
|
||||
must_list.append(watch_must_list)
|
||||
|
||||
video_type = self.request_params.get("type")
|
||||
if video_type:
|
||||
type_list_list = self.parse_type(video_type[0])
|
||||
type_list_list = self.parse_type(video_type)
|
||||
must_list.append(type_list_list)
|
||||
|
||||
query = {"bool": {"must": must_list}}
|
||||
|
|
@ -88,14 +88,12 @@ class QueryBuilder:
|
|||
if not sort:
|
||||
return None
|
||||
|
||||
sort = sort[0]
|
||||
if not hasattr(SortEnum, sort.upper()):
|
||||
raise ValueError(f"'{sort}' not in SortEnum")
|
||||
|
||||
sort_field = getattr(SortEnum, sort.upper()).value
|
||||
|
||||
order = self.request_params.get("order", ["desc"])
|
||||
order = order[0]
|
||||
if not hasattr(OrderEnum, order.upper()):
|
||||
raise ValueError(f"'{order}' not in OrderEnum")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,22 @@
|
|||
"""all API views for video endpoints"""
|
||||
|
||||
from common.serializers import ErrorResponseSerializer
|
||||
from common.src.helper import calc_is_watched
|
||||
from common.src.ta_redis import RedisArchivist
|
||||
from common.src.watched import WatchState
|
||||
from common.views_base import AdminWriteOnly, ApiBaseView
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from playlist.src.index import YoutubePlaylist
|
||||
from rest_framework.response import Response
|
||||
from video.serializers import (
|
||||
CommentItemSerializer,
|
||||
PlayerSerializer,
|
||||
PlaylistNavItemSerializer,
|
||||
VideoListQuerySerializer,
|
||||
VideoListSerializer,
|
||||
VideoProgressUpdateSerializer,
|
||||
VideoSerializer,
|
||||
)
|
||||
from video.src.index import YoutubeVideo
|
||||
from video.src.query_building import QueryBuilder
|
||||
|
||||
|
|
@ -24,13 +35,22 @@ class VideoApiListView(ApiBaseView):
|
|||
|
||||
search_base = "ta_video/_search/"
|
||||
|
||||
@extend_schema(
|
||||
parameters=[VideoListQuerySerializer()],
|
||||
responses={
|
||||
200: VideoListSerializer(),
|
||||
400: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="bad request"
|
||||
),
|
||||
},
|
||||
)
|
||||
def get(self, request):
|
||||
"""get request"""
|
||||
try:
|
||||
data = QueryBuilder(request.user.id, **request.GET).build_data()
|
||||
except ValueError as err:
|
||||
return Response({"error": str(err)}, status=400)
|
||||
"""get video list"""
|
||||
query_serializer = VideoListQuerySerializer(data=request.query_params)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
validated_query = query_serializer.validated_data
|
||||
|
||||
data = QueryBuilder(request.user.id, **validated_query).build_data()
|
||||
if data == {"query": {"bool": {"must": [None]}}}:
|
||||
# skip empty lookup
|
||||
return Response([])
|
||||
|
|
@ -38,7 +58,9 @@ class VideoApiListView(ApiBaseView):
|
|||
self.data = data
|
||||
self.get_document_list(request, progress_match=request.user.id)
|
||||
|
||||
return Response(self.response)
|
||||
response_serializer = VideoListSerializer(self.response)
|
||||
|
||||
return Response(response_serializer.data)
|
||||
|
||||
|
||||
class VideoApiView(ApiBaseView):
|
||||
|
|
@ -49,25 +71,71 @@ class VideoApiView(ApiBaseView):
|
|||
search_base = "ta_video/_doc/"
|
||||
permission_classes = [AdminWriteOnly]
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: VideoSerializer(),
|
||||
404: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="video not found"
|
||||
),
|
||||
},
|
||||
)
|
||||
def get(self, request, video_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
"""get video"""
|
||||
self.get_document(video_id, progress_match=request.user.id)
|
||||
return Response(self.response, status=self.status_code)
|
||||
if not self.response:
|
||||
error = ErrorResponseSerializer({"error": "video not found"})
|
||||
return Response(error.data, status=404)
|
||||
|
||||
serializer = VideoSerializer(self.response)
|
||||
return Response(serializer.data)
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
204: OpenApiResponse(description="video deleted"),
|
||||
404: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="video not found"
|
||||
),
|
||||
}
|
||||
)
|
||||
def delete(self, request, video_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""delete single video"""
|
||||
message = {"video": video_id}
|
||||
"""delete video"""
|
||||
try:
|
||||
YoutubeVideo(video_id).delete_media_file()
|
||||
status_code = 200
|
||||
message.update({"state": "delete"})
|
||||
except FileNotFoundError:
|
||||
status_code = 404
|
||||
message.update({"state": "not found"})
|
||||
error = ErrorResponseSerializer({"error": "video not found"})
|
||||
return Response(error.data, status=404)
|
||||
|
||||
return Response(message, status=status_code)
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class VideoCommentView(ApiBaseView):
|
||||
"""resolves to /api/video/<video_id>/comment/
|
||||
handle video comments
|
||||
GET: return all comments from video with reply threads
|
||||
"""
|
||||
|
||||
search_base = "ta_comment/_doc/"
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: CommentItemSerializer(),
|
||||
404: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="video not found"
|
||||
),
|
||||
}
|
||||
)
|
||||
def get(self, request, video_id):
|
||||
"""get video comments"""
|
||||
# pylint: disable=unused-argument
|
||||
self.get_document(video_id)
|
||||
if self.status_code == 404:
|
||||
error = ErrorResponseSerializer({"error": "video not found"})
|
||||
return Response(error.data, status=404)
|
||||
|
||||
serializer = CommentItemSerializer(self.response, many=True)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class VideoApiNavView(ApiBaseView):
|
||||
|
|
@ -77,26 +145,39 @@ class VideoApiNavView(ApiBaseView):
|
|||
|
||||
search_base = "ta_video/_doc/"
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
200: PlaylistNavItemSerializer(),
|
||||
404: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="video not found"
|
||||
),
|
||||
}
|
||||
)
|
||||
def get(self, request, video_id):
|
||||
# pylint: disable=unused-argument
|
||||
"""get request"""
|
||||
"""get video playlist nav"""
|
||||
self.get_document(video_id)
|
||||
if self.status_code != 200:
|
||||
return Response(status=self.status_code)
|
||||
if self.status_code == 404:
|
||||
error = ErrorResponseSerializer({"error": "video not found"})
|
||||
return Response(error.data, status=404)
|
||||
|
||||
playlist_nav = []
|
||||
|
||||
if not self.response["data"].get("playlist"):
|
||||
if not self.response.get("playlist"):
|
||||
return Response(playlist_nav)
|
||||
|
||||
for playlist_id in self.response["data"]["playlist"]:
|
||||
for playlist_id in self.response["playlist"]:
|
||||
playlist = YoutubePlaylist(playlist_id)
|
||||
playlist.get_from_es()
|
||||
playlist.build_nav(video_id)
|
||||
if playlist.nav:
|
||||
playlist_nav.append(playlist.nav)
|
||||
|
||||
return Response(playlist_nav, status=self.status_code)
|
||||
response_serializer = PlaylistNavItemSerializer(
|
||||
playlist_nav, many=True
|
||||
)
|
||||
|
||||
return Response(response_serializer.data)
|
||||
|
||||
|
||||
class VideoProgressView(ApiBaseView):
|
||||
|
|
@ -111,19 +192,32 @@ class VideoProgressView(ApiBaseView):
|
|||
"""redis key"""
|
||||
return f"{user_id}:progress:{video_id}"
|
||||
|
||||
@extend_schema(
|
||||
request=VideoProgressUpdateSerializer(),
|
||||
responses={
|
||||
200: PlayerSerializer(),
|
||||
404: OpenApiResponse(
|
||||
ErrorResponseSerializer(), description="video not found"
|
||||
),
|
||||
},
|
||||
)
|
||||
def post(self, request, video_id):
|
||||
"""set progress position in redis"""
|
||||
position = request.data.get("position", 0)
|
||||
"""set video progress position in redis"""
|
||||
data_serializer = VideoProgressUpdateSerializer(data=request.data)
|
||||
data_serializer.is_valid(raise_exception=True)
|
||||
validated_data = data_serializer.validated_data
|
||||
|
||||
self.get_document(video_id)
|
||||
if self.status_code == 404:
|
||||
error = ErrorResponseSerializer({"error": "video not found"})
|
||||
return Response(error.data, status=404)
|
||||
|
||||
position = validated_data["position"]
|
||||
key = self._get_key(request.user.id, video_id)
|
||||
redis_con = RedisArchivist()
|
||||
current_progress = redis_con.get_message_dict(key)
|
||||
|
||||
if not current_progress:
|
||||
self.get_document(video_id)
|
||||
if self.status_code != 200:
|
||||
return Response(status=self.status_code)
|
||||
|
||||
current_progress = self.response["data"]["player"]
|
||||
current_progress = (
|
||||
redis_con.get_message_dict(key) or self.response["player"]
|
||||
)
|
||||
|
||||
current_progress.update({"position": position, "youtube_id": video_id})
|
||||
watched = self._check_watched(request, video_id, current_progress)
|
||||
|
|
@ -134,8 +228,11 @@ class VideoProgressView(ApiBaseView):
|
|||
|
||||
current_progress.update({"watched": watched})
|
||||
redis_con.set_message(key, current_progress, expire=expire)
|
||||
print(current_progress)
|
||||
|
||||
return Response(current_progress)
|
||||
response_serializer = PlayerSerializer(current_progress)
|
||||
|
||||
return Response(response_serializer.data)
|
||||
|
||||
def _check_watched(self, request, video_id, current_progress) -> bool:
|
||||
"""check watched state"""
|
||||
|
|
@ -150,29 +247,17 @@ class VideoProgressView(ApiBaseView):
|
|||
|
||||
return watched
|
||||
|
||||
@extend_schema(
|
||||
responses={
|
||||
204: OpenApiResponse(description="video progress deleted"),
|
||||
}
|
||||
)
|
||||
def delete(self, request, video_id):
|
||||
"""delete progress position"""
|
||||
key = self._get_key(request.user.id, video_id)
|
||||
RedisArchivist().del_message(key)
|
||||
self.response = {"progress-reset": video_id}
|
||||
|
||||
return Response(self.response)
|
||||
|
||||
|
||||
class VideoCommentView(ApiBaseView):
|
||||
"""resolves to /api/video/<video_id>/comment/
|
||||
handle video comments
|
||||
GET: return all comments from video with reply threads
|
||||
"""
|
||||
|
||||
search_base = "ta_comment/_doc/"
|
||||
|
||||
def get(self, request, video_id):
|
||||
"""get video comments"""
|
||||
# pylint: disable=unused-argument
|
||||
self.get_document(video_id)
|
||||
|
||||
return Response(self.response, status=200)
|
||||
return Response(status=204)
|
||||
|
||||
|
||||
class VideoSimilarView(ApiBaseView):
|
||||
|
|
@ -182,6 +267,9 @@ class VideoSimilarView(ApiBaseView):
|
|||
|
||||
search_base = "ta_video/_search/"
|
||||
|
||||
@extend_schema(
|
||||
responses=VideoSerializer(many=True),
|
||||
)
|
||||
def get(self, request, video_id):
|
||||
"""get similar videos"""
|
||||
self.data = {
|
||||
|
|
@ -196,4 +284,5 @@ class VideoSimilarView(ApiBaseView):
|
|||
},
|
||||
}
|
||||
self.get_document_list(request, pagination=False)
|
||||
return Response(self.response, status=200)
|
||||
serializer = VideoSerializer(self.response["data"], many=True)
|
||||
return Response(serializer.data)
|
||||
|
|
|
|||
Loading…
Reference in New Issue