diff --git a/tubearchivist/home/config.json b/tubearchivist/home/config.json index 9ffe376..3e5e941 100644 --- a/tubearchivist/home/config.json +++ b/tubearchivist/home/config.json @@ -8,7 +8,8 @@ "home": "grid", "channel": "list", "downloads": "list", - "playlist": "grid" + "playlist": "grid", + "grid_items": 3 }, "subscriptions": { "auto_search": false, diff --git a/tubearchivist/home/src/download/queue.py b/tubearchivist/home/src/download/queue.py index 36e3d1e..47225dd 100644 --- a/tubearchivist/home/src/download/queue.py +++ b/tubearchivist/home/src/download/queue.py @@ -172,6 +172,8 @@ class PendingList(PendingIndex): """add video to list""" if url not in self.missing_videos and url not in self.to_skip: self.missing_videos.append(url) + else: + print(f"{url}: skipped adding already indexed video to download.") def _parse_channel(self, url): """add all videos of channel to list""" diff --git a/tubearchivist/home/src/download/subscriptions.py b/tubearchivist/home/src/download/subscriptions.py index 973c76e..916c170 100644 --- a/tubearchivist/home/src/download/subscriptions.py +++ b/tubearchivist/home/src/download/subscriptions.py @@ -43,7 +43,8 @@ class ChannelSubscription: if limit: obs["playlistend"] = self.config["subscriptions"]["channel_size"] - channel = YtWrap(obs, self.config).extract(channel_id) + url = f"https://www.youtube.com/channel/{channel_id}/videos" + channel = YtWrap(obs, self.config).extract(url) if not channel: return False diff --git a/tubearchivist/home/src/frontend/api_calls.py b/tubearchivist/home/src/frontend/api_calls.py index 8b0d9b3..ed937ae 100644 --- a/tubearchivist/home/src/frontend/api_calls.py +++ b/tubearchivist/home/src/frontend/api_calls.py @@ -54,6 +54,7 @@ class PostData: "watched": self._watched, "un_watched": self._un_watched, "change_view": self._change_view, + "change_grid": self._change_grid, "rescan_pending": self._rescan_pending, "ignore": self._ignore, "dl_pending": self._dl_pending, @@ -100,6 +101,17 @@ class PostData: RedisArchivist().set_message(key, {"status": new_view}, expire=False) return {"success": True} + def _change_grid(self): + """process change items in grid""" + grid_items = int(self.exec_val) + grid_items = max(grid_items, 3) + grid_items = min(grid_items, 7) + + key = f"{self.current_user}:grid_items" + print(f"change grid items: {grid_items}") + RedisArchivist().set_message(key, {"status": grid_items}, expire=False) + return {"success": True} + @staticmethod def _rescan_pending(): """look for new items in subscribed channels""" diff --git a/tubearchivist/home/src/index/video.py b/tubearchivist/home/src/index/video.py index 7b951d8..fa2b0f9 100644 --- a/tubearchivist/home/src/index/video.py +++ b/tubearchivist/home/src/index/video.py @@ -12,6 +12,7 @@ import requests from django.conf import settings from home.src.es.connect import ElasticWrap from home.src.index import channel as ta_channel +from home.src.index import playlist as ta_playlist from home.src.index.generic import YouTubeItem from home.src.ta.helper import ( DurationConverter, @@ -189,9 +190,9 @@ class SubtitleParser: self.all_cues = [] for idx, event in enumerate(all_events): - if "dDurationMs" not in event: - # some events won't have a duration - print(f"failed to parse event without duration: {event}") + if "dDurationMs" not in event or "segs" not in event: + # some events won't have a duration or segs + print(f"skipping subtitle event without content: {event}") continue cue = { @@ -215,15 +216,16 @@ 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}") + last = flatten[-1] + if "dDurationMs" not in last or "segs" not in last: + # some events won't have a duration or segs + print(f"skipping subtitle event without content: {event}") continue - last_end = flatten[-1]["tStartMs"] + flatten[-1]["dDurationMs"] + last_end = last["tStartMs"] + last["dDurationMs"] if event["tStartMs"] < last_end: - joined = flatten[-1]["segs"][0]["utf8"] + "\n" + text - flatten[-1]["segs"][0]["utf8"] = joined + joined = last["segs"][0]["utf8"] + "\n" + text + last["segs"][0]["utf8"] = joined continue event.update({"segs": [{"utf8": text}]}) @@ -560,6 +562,7 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): def delete_media_file(self): """delete video file, meta data""" + print(f"{self.youtube_id}: delete video") self.get_from_es() video_base = self.app_conf["videos"] media_url = self.json_data.get("media_url") @@ -569,9 +572,28 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle): except FileNotFoundError: print(f"{self.youtube_id}: failed {media_url}, continue.") + self.del_in_playlists() self.del_in_es() self.delete_subtitles() + def del_in_playlists(self): + """remove downloaded in playlist""" + all_playlists = self.json_data.get("playlist") + if not all_playlists: + return + + for playlist_id in all_playlists: + print(f"{playlist_id}: delete video {self.youtube_id}") + playlist = ta_playlist.YoutubePlaylist(playlist_id) + playlist.get_from_es() + entries = playlist.json_data["playlist_entries"] + for idx, entry in enumerate(entries): + if entry["youtube_id"] == self.youtube_id: + playlist.json_data["playlist_entries"][idx].update( + {"downloaded": False} + ) + playlist.upload_to_es() + def delete_subtitles(self): """delete indexed subtitles""" print(f"{self.youtube_id}: delete subtitles") diff --git a/tubearchivist/home/templates/home/channel_id.html b/tubearchivist/home/templates/home/channel_id.html index 8240132..04a4333 100644 --- a/tubearchivist/home/templates/home/channel_id.html +++ b/tubearchivist/home/templates/home/channel_id.html @@ -112,6 +112,8 @@ {% endif %} + +
Hide watched videos: @@ -141,14 +143,21 @@
sort-icon + {% if view_style == "grid" %} +
+ {{ grid_items }} + grid plus row + grid minus row +
+ {% endif %} grid view list view
-
-
+
+
{% if results %} {% for video in results %}
diff --git a/tubearchivist/home/templates/home/home.html b/tubearchivist/home/templates/home/home.html index 445de5b..fc4c76a 100644 --- a/tubearchivist/home/templates/home/home.html +++ b/tubearchivist/home/templates/home/home.html @@ -1,12 +1,12 @@ {% extends "home/base.html" %} {% block content %} {% load static %} -
+
{% if continue_vids %}

Continue Watching

-
-
-
+
+
{% if results %} {% for video in results %}
diff --git a/tubearchivist/home/templates/home/playlist_id.html b/tubearchivist/home/templates/home/playlist_id.html index f9e987e..60b5522 100644 --- a/tubearchivist/home/templates/home/playlist_id.html +++ b/tubearchivist/home/templates/home/playlist_id.html @@ -63,6 +63,8 @@
{% endif %} +
+
Hide watched videos: @@ -76,14 +78,21 @@
+ {% if view_style == "grid" %} +
+ {{ grid_items }} + grid plus row + grid minus row +
+ {% endif %} grid view list view
-
-
+
+
{% if results %} {% for video in results %}
diff --git a/tubearchivist/home/views.py b/tubearchivist/home/views.py index bd5a2ea..30738d2 100644 --- a/tubearchivist/home/views.py +++ b/tubearchivist/home/views.py @@ -77,6 +77,15 @@ class ArchivistViewConfig(View): return view_style + def _get_grid_items(self): + """return items per row to show in grid view""" + grid_key = f"{self.user_id}:grid_items" + grid_items = self.user_conf.get_message(grid_key)["status"] + if not grid_items: + grid_items = self.default_conf["default_view"]["grid_items"] + + return grid_items + def get_all_view_styles(self): """get dict of all view stiles for search form""" all_keys = ["channel", "playlist", "home"] @@ -120,6 +129,7 @@ class ArchivistViewConfig(View): "sort_by": self._get_sort_by(), "sort_order": self._get_sort_order(), "view_style": self._get_view_style(), + "grid_items": self._get_grid_items(), "hide_watched": self._get_hide_watched(), "show_ignored_only": self._get_show_ignore_only(), "show_subed_only": self._get_show_subed_only(), diff --git a/tubearchivist/static/css/style.css b/tubearchivist/static/css/style.css index d92187a..5ed455d 100644 --- a/tubearchivist/static/css/style.css +++ b/tubearchivist/static/css/style.css @@ -133,6 +133,18 @@ button:hover { margin: 0 auto; } +.boxed-content.boxed-4 { + max-width: 1200px; + width: 80%; +} + +.boxed-content.boxed-5, +.boxed-content.boxed-6, +.boxed-content.boxed-7 { + max-width: unset; + width: 85%; +} + .round-img img { border-radius: 50%; } @@ -328,12 +340,14 @@ button:hover { margin: 15px 0; } -.view-icons { +.view-icons, +.grid-count { display: flex; justify-content: end; } -.view-icons img { +.view-icons img, +.grid-count img { width: 30px; margin: 5px 10px; cursor: pointer; @@ -395,10 +409,26 @@ button:hover { margin-top: 1rem; } -.video-list.grid { +.video-list.grid.grid-3 { grid-template-columns: 1fr 1fr 1fr; } +.video-list.grid.grid-4 { + grid-template-columns: 1fr 1fr 1fr 1fr; +} + +.video-list.grid.grid-5 { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr; +} + +.video-list.grid.grid-6 { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; +} + +.video-list.grid.grid-7 { + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr; +} + .video-list.list { grid-template-columns: unset; } @@ -1054,7 +1084,11 @@ button:hover { .boxed-content { width: 90%; } - .video-list.grid, + .video-list.grid.grid-3, + .video-list.grid.grid-4, + .video-list.grid.grid-5, + .video-list.grid.grid-6, + .video-list.grid.grid-7, .dl-list.grid, .channel-list.grid, .playlist-list.grid { @@ -1084,6 +1118,9 @@ button:hover { position: unset; transform: unset; } + .grid-count { + display: none; + } .video-player { height: unset; padding: 20px 0 @@ -1098,7 +1135,11 @@ button:hover { * { word-wrap: anywhere; } - .video-list.grid, + .video-list.grid.grid-3, + .video-list.grid.grid-4, + .video-list.grid.grid-5, + .video-list.grid.grid-6, + .video-list.grid.grid-7, .dl-list.grid, .channel-list.grid, .video-item.list, diff --git a/tubearchivist/static/img/icon-substract.svg b/tubearchivist/static/img/icon-substract.svg new file mode 100644 index 0000000..0b68cfb --- /dev/null +++ b/tubearchivist/static/img/icon-substract.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index e20d31c..9c016be 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -121,6 +121,16 @@ function changeView(image) { }, 500); } +function changeGridItems(image) { + var operator = image.getAttribute("data-value"); + var payload = JSON.stringify({'change_grid': operator}); + sendPost(payload); + setTimeout(function(){ + location.reload(); + return false; + }, 500); +} + function toggleCheckbox(checkbox) { // pass checkbox id as key and checkbox.checked as value var toggleId = checkbox.id;