From 3147df20da96efd01e23bdd27300176bdeba2177 Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 18 Apr 2022 11:52:13 +0700 Subject: [PATCH 1/4] skip subtitle segments without duration, take 2 --- tubearchivist/home/src/index/video.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tubearchivist/home/src/index/video.py b/tubearchivist/home/src/index/video.py index 969d716..290e0ce 100644 --- a/tubearchivist/home/src/index/video.py +++ b/tubearchivist/home/src/index/video.py @@ -195,6 +195,11 @@ class SubtitleParser: if flatten: # fix overlapping retiming issue + if "dDurationMs" not in flatten[-1]: + # some events won't have a duration + print(f"failed to parse event without duration: {event}") + continue + last_end = flatten[-1]["tStartMs"] + flatten[-1]["dDurationMs"] if event["tStartMs"] < last_end: joined = flatten[-1]["segs"][0]["utf8"] + "\n" + text From 73052164852550b0f089a31ed58eb696056cd02a Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 19 Apr 2022 08:07:47 +0700 Subject: [PATCH 2/4] add link to ES documentation for disk usage --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d3ee806..16a6112 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,9 @@ chown 1000:0 /path/to/mount/point This will match the permissions with the **UID** and **GID** of elasticsearch within the container and should fix the issue. ### Disk usage -The Elasticsearch index will turn to *read only* if the disk usage of the container goes above 95% until the usage drops below 90% again. Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download. +The Elasticsearch index will turn to *read only* if the disk usage of the container goes above 95% until the usage drops below 90% again, you will see error messages like `disk usage exceeded flood-stage watermark`, [link](https://github.com/tubearchivist/tubearchivist#disk-usage). + +Similar to that, TubeArchivist will become all sorts of messed up when running out of disk space. There are some error messages in the logs when that happens, but it's best to make sure to have enough disk space before starting to download. ## Getting Started 1. Go through the **settings** page and look at the available options. Particularly set *Download Format* to your desired video quality before downloading. **Tube Archivist** downloads the best available quality by default. To support iOS or MacOS and some other browsers a compatible format must be specified. For example: From 04fc6ed26a9df4d4c12142397175c615eb0e0972 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 20 Apr 2022 22:43:07 +0700 Subject: [PATCH 3/4] API: add pagination --- tubearchivist/api/README.md | 13 ++++ tubearchivist/api/views.py | 82 +++++++++++++++---------- tubearchivist/home/src/index/generic.py | 1 + 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/tubearchivist/api/README.md b/tubearchivist/api/README.md index c635b11..e2bed71 100644 --- a/tubearchivist/api/README.md +++ b/tubearchivist/api/README.md @@ -20,6 +20,19 @@ headers = {"Authorization": "Token xxxxxxxxxx"} response = requests.get(url, headers=headers) ``` +## Pagination +The list views return a paginate object with the following keys: +- page_size: int current page size set in config +- page_from: int first result idx +- prev_pages: array of ints of previous pages, if available +- current_page: int current page from query +- max_hits: reached: bool if max of 10k results is reached +- last_page: int of last page link +- next_pages: array of ints of next pages +- total_hits: int total results + +Pass page number as a query parameter: `page=2`. Defaults to *0*, `page=1` is redundant and falls back to *0*. If a page query doesn't return any results, you'll get `HTTP 404 Not Found`. + ## Login View Return token and user ID for username and password: POST /api/login diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index a30484e..ea60424 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -3,6 +3,7 @@ from api.src.search_processor import SearchProcess from home.src.download.queue import PendingInteract from home.src.es.connect import ElasticWrap +from home.src.index.generic import Pagination from home.src.index.video import SponsorBlock from home.src.ta.config import AppConfig from home.src.ta.helper import UrlListParser @@ -25,12 +26,14 @@ class ApiBaseView(APIView): authentication_classes = [SessionAuthentication, TokenAuthentication] permission_classes = [IsAuthenticated] search_base = False + data = {"query": {"match_all": {}}} def __init__(self): super().__init__() self.response = {"data": False, "config": AppConfig().config} self.status_code = False self.context = False + self.pagination_handler = False def get_document(self, document_id): """get single document from es""" @@ -44,20 +47,33 @@ class ApiBaseView(APIView): self.response["data"] = False self.status_code = status_code - def get_paginate(self): - """add pagination detail to response""" - self.response["paginate"] = False + def initiate_pagination(self, request): + """set initial pagination values""" + user_id = request.user.id + page_get = int(request.GET.get("page", 0)) + self.pagination_handler = Pagination(page_get, user_id) + self.data.update( + { + "size": self.pagination_handler.pagination["page_size"], + "from": self.pagination_handler.pagination["page_from"], + } + ) - def get_document_list(self, data): + def get_document_list(self, request): """get a list of results""" print(self.search_base) - response, status_code = ElasticWrap(self.search_base).get(data=data) + self.initiate_pagination(request) + es_handler = ElasticWrap(self.search_base) + response, status_code = es_handler.get(data=self.data) self.response["data"] = SearchProcess(response).process() if self.response["data"]: self.status_code = status_code else: self.status_code = 404 + self.pagination_handler.validate(response["hits"]["total"]["value"]) + self.response["paginate"] = self.pagination_handler.pagination + class VideoApiView(ApiBaseView): """resolves to /api/video// @@ -81,11 +97,9 @@ class VideoApiListView(ApiBaseView): search_base = "ta_video/_search/" def get(self, request): - # pylint: disable=unused-argument """get request""" - data = {"query": {"match_all": {}}} - self.get_document_list(data) - self.get_paginate() + self.data.update({"sort": [{"published": {"order": "desc"}}]}) + self.get_document_list(request) return Response(self.response) @@ -200,11 +214,11 @@ class ChannelApiListView(ApiBaseView): search_base = "ta_channel/_search/" def get(self, request): - # pylint: disable=unused-argument """get request""" - data = {"query": {"match_all": {}}} - self.get_document_list(data) - self.get_paginate() + self.get_document_list(request) + self.data.update( + {"sort": [{"channel_name.keyword": {"order": "asc"}}]} + ) return Response(self.response) @@ -234,13 +248,16 @@ class ChannelApiVideoView(ApiBaseView): search_base = "ta_video/_search/" def get(self, request, channel_id): - # pylint: disable=unused-argument """handle get request""" - data = { - "query": {"term": {"channel.channel_id": {"value": channel_id}}} - } - self.get_document_list(data) - self.get_paginate() + self.data.update( + { + "query": { + "term": {"channel.channel_id": {"value": channel_id}} + }, + "sort": [{"published": {"order": "desc"}}], + } + ) + self.get_document_list(request) return Response(self.response, status=self.status_code) @@ -253,11 +270,11 @@ class PlaylistApiListView(ApiBaseView): search_base = "ta_playlist/_search/" def get(self, request): - # pylint: disable=unused-argument """handle get request""" - data = {"query": {"match_all": {}}} - self.get_document_list(data) - self.get_paginate() + self.data.update( + {"sort": [{"playlist_name.keyword": {"order": "asc"}}]} + ) + self.get_document_list(request) return Response(self.response) @@ -283,13 +300,13 @@ class PlaylistApiVideoView(ApiBaseView): search_base = "ta_video/_search/" def get(self, request, playlist_id): - # pylint: disable=unused-argument """handle get request""" - data = { - "query": {"term": {"playlist.keyword": {"value": playlist_id}}} + self.data["query"] = { + "term": {"playlist.keyword": {"value": playlist_id}} } - self.get_document_list(data) - self.get_paginate() + self.data.update({"sort": [{"published": {"order": "desc"}}]}) + + self.get_document_list(request) return Response(self.response, status=self.status_code) @@ -344,11 +361,9 @@ class DownloadApiListView(ApiBaseView): valid_filter = ["pending", "ignore"] def get(self, request): - # pylint: disable=unused-argument """get request""" query_filter = request.GET.get("filter", False) - data = { - "query": {"match_all": {}}, + self.data.update = { "sort": [{"timestamp": {"order": "asc"}}], } if query_filter: @@ -357,10 +372,9 @@ class DownloadApiListView(ApiBaseView): print(message) return Response({"message": message}, status=400) - data["query"] = {"term": {"status": {"value": query_filter}}} + self.data["query"] = {"term": {"status": {"value": query_filter}}} - self.get_document_list(data) - self.get_paginate() + self.get_document_list(request) return Response(self.response) @staticmethod diff --git a/tubearchivist/home/src/index/generic.py b/tubearchivist/home/src/index/generic.py index dcff82b..709dde9 100644 --- a/tubearchivist/home/src/index/generic.py +++ b/tubearchivist/home/src/index/generic.py @@ -147,3 +147,4 @@ class Pagination: ] self.pagination["next_pages"] = next_pages + self.pagination["total_hits"] = total_hits From 1477370376212dc4bfa6c5e80764f10f47fddc51 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 20 Apr 2022 22:51:30 +0700 Subject: [PATCH 4/4] init data instead of class attribute --- tubearchivist/api/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py index ea60424..0fe1d7f 100644 --- a/tubearchivist/api/views.py +++ b/tubearchivist/api/views.py @@ -26,11 +26,12 @@ class ApiBaseView(APIView): authentication_classes = [SessionAuthentication, TokenAuthentication] permission_classes = [IsAuthenticated] search_base = False - data = {"query": {"match_all": {}}} + data = False def __init__(self): super().__init__() self.response = {"data": False, "config": AppConfig().config} + self.data = {"query": {"match_all": {}}} self.status_code = False self.context = False self.pagination_handler = False