diff --git a/Dockerfile b/Dockerfile index 6b2a3a22..929967ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # First stage to build python wheel -FROM python:3.10.7-slim-bullseye AS builder +FROM python:3.10.8-slim-bullseye AS builder ARG TARGETPLATFORM RUN apt-get update diff --git a/tubearchivist/home/src/download/thumbnails.py b/tubearchivist/home/src/download/thumbnails.py index ff556c09..e91a97db 100644 --- a/tubearchivist/home/src/download/thumbnails.py +++ b/tubearchivist/home/src/download/thumbnails.py @@ -14,7 +14,7 @@ from home.src.download import queue # partial import from home.src.es.connect import IndexPaginate from home.src.ta.config import AppConfig from mutagen.mp4 import MP4, MP4Cover -from PIL import Image, ImageFile, ImageFilter +from PIL import Image, ImageFile, ImageFilter, UnidentifiedImageError ImageFile.LOAD_TRUNCATED_IMAGES = True @@ -42,7 +42,12 @@ class ThumbManagerBase: try: response = requests.get(url, stream=True, timeout=5) if response.ok: - return Image.open(response.raw) + try: + return Image.open(response.raw) + except UnidentifiedImageError: + print(f"failed to open thumbnail: {url}") + return self.get_fallback() + if response.status_code == 404: return self.get_fallback() diff --git a/tubearchivist/home/src/frontend/watched.py b/tubearchivist/home/src/frontend/watched.py index 85aa1abe..88f694f8 100644 --- a/tubearchivist/home/src/frontend/watched.py +++ b/tubearchivist/home/src/frontend/watched.py @@ -57,6 +57,14 @@ class WatchState: print(response) raise ValueError("failed to mark video as watched") + def _get_source(self): + """build source line for update_by_query script""" + source = [ + "ctx._source.player['watched'] = true", + f"ctx._source.player['watched_date'] = {self.stamp}", + ] + return "; ".join(source) + def mark_channel_watched(self): """change watched status of every video in channel""" path = "ta_video/_update_by_query" @@ -67,7 +75,7 @@ class WatchState: data = { "query": {"bool": {"must": must_list}}, "script": { - "source": "ctx._source.player['watched'] = true", + "source": self._get_source(), "lang": "painless", }, } @@ -87,7 +95,7 @@ class WatchState: data = { "query": {"bool": {"must": must_list}}, "script": { - "source": "ctx._source.player['watched'] = true", + "source": self._get_source(), "lang": "painless", }, } diff --git a/tubearchivist/home/templates/home/downloads.html b/tubearchivist/home/templates/home/downloads.html index 85d61e7c..22dd6fdf 100644 --- a/tubearchivist/home/templates/home/downloads.html +++ b/tubearchivist/home/templates/home/downloads.html @@ -41,6 +41,15 @@
+ {% if channel_agg_list|length > 1 %} + Filter: + + {% endif %} {% if view_style == "grid" %}
{% if grid_items < 7 %} diff --git a/tubearchivist/home/views.py b/tubearchivist/home/views.py index 9d44399d..ba5af2f1 100644 --- a/tubearchivist/home/views.py +++ b/tubearchivist/home/views.py @@ -364,6 +364,7 @@ class DownloadView(ArchivistResultsView): { "title": "Downloads", "add_form": AddToQueueForm(), + "channel_agg_list": self._get_channel_agg(), } ) return render(request, "home/downloads.html", self.context) @@ -399,6 +400,38 @@ class DownloadView(ArchivistResultsView): } ) + def _get_channel_agg(self): + """get pending channel with count""" + data = { + "size": 0, + "query": {"term": {"status": {"value": "pending"}}}, + "aggs": { + "channel_downloads": { + "multi_terms": { + "size": 30, + "terms": [ + {"field": "channel_name.keyword"}, + {"field": "channel_id"}, + ], + "order": {"_count": "desc"}, + } + } + }, + } + response, _ = ElasticWrap(self.es_search).get(data=data) + buckets = response["aggregations"]["channel_downloads"]["buckets"] + + buckets_sorted = [] + for i in buckets: + bucket = { + "name": i["key"][0], + "id": i["key"][1], + "count": i["doc_count"], + } + buckets_sorted.append(bucket) + + return buckets_sorted + @staticmethod def post(request): """handle post requests""" diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css index e4f90472..7a3872e3 100644 --- a/tubearchivist/static/css/style.css +++ b/tubearchivist/static/css/style.css @@ -344,6 +344,7 @@ button:hover { .grid-count { display: flex; justify-content: end; + align-items: center; } .view-icons img { diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index 7e375c17..a1205d68 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -1102,12 +1102,12 @@ function textReveal() { function textExpand() { var textBox = document.getElementById("text-expand"); var button = document.getElementById("text-expand-button"); - var textBoxLineClamp = textBox.style["-webkit-line-clamp"]; - if (textBoxLineClamp === "none") { + var style = window.getComputedStyle(textBox) + if (style.webkitLineClamp === "none") { textBox.style["-webkit-line-clamp"] = "4"; button.innerText = "Show more"; } else { - textBox.style["-webkit-line-clamp"] = "none"; + textBox.style["-webkit-line-clamp"] = "unset"; button.innerText = "Show less"; } } @@ -1119,8 +1119,9 @@ function textExpandButtonVisibilityUpdate() { if (!textBox || !button) return; - var textBoxLineClamp = textBox.style["-webkit-line-clamp"]; - if (textBoxLineClamp === "none") + var styles = window.getComputedStyle(textBox); + var textBoxLineClamp = styles.webkitLineClamp; + if (textBoxLineClamp === "unset") return; // text box is in revealed state if (textBox.offsetHeight < textBox.scrollHeight @@ -1147,6 +1148,14 @@ function showForm() { animate('animate-icon', 'pulse-img'); } +function channelFilterDownload(value) { + if (value === "all") { + window.location = "/downloads/"; + } else { + window.location.search = "?channel=" + value; + } +} + function showOverwrite() { var overwriteDiv = document.getElementById("overwrite-form"); if (overwriteDiv.classList.contains("hidden-overwrite")) {