variable gird row items, #build

Changed:
- implemented configurable video items in grid row
- fix subtitle parser bug with missing segs key
- fix also delete video from playlist
- fix channel video extractor after refactor
This commit is contained in:
simon 2022-05-28 21:54:47 +07:00
commit 386c456415
No known key found for this signature in database
GPG Key ID: 2C15AA5E89985DD4
12 changed files with 223 additions and 24 deletions

View File

@ -8,7 +8,8 @@
"home": "grid",
"channel": "list",
"downloads": "list",
"playlist": "grid"
"playlist": "grid",
"grid_items": 3
},
"subscriptions": {
"auto_search": false,

View File

@ -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"""

View File

@ -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

View File

@ -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"""

View File

@ -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")

View File

@ -112,6 +112,8 @@
</div>
</div>
{% endif %}
</div>
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
<div class="view-controls">
<div class="toggle">
<span>Hide watched videos:</span>
@ -141,14 +143,21 @@
</div>
<div class="view-icons">
<img src="{% static 'img/icon-sort.svg' %}" alt="sort-icon" onclick="showForm()" id="animate-icon">
{% if view_style == "grid" %}
<div class="grid-count">
<span>{{ grid_items }}</span>
<img src="{% static 'img/icon-add.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"1"}}" alt="grid plus row">
<img src="{% static 'img/icon-substract.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"-1"}}" alt="grid minus row">
</div>
{% endif %}
<img src="{% static 'img/icon-gridview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="grid" alt="grid view">
<img src="{% static 'img/icon-listview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="list" alt="list view">
</div>
</div>
</div>
<div id="player" class="player-wrapper"></div>
<div class="boxed-content">
<div class="video-list {{ view_style }}">
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
<div class="video-list {{ view_style }} {% if view_style == "grid" %}grid-{{ grid_items }}{% endif %}">
{% if results %}
{% for video in results %}
<div class="video-item {{ view_style }}">

View File

@ -1,12 +1,12 @@
{% extends "home/base.html" %}
{% block content %}
{% load static %}
<div class="boxed-content">
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
{% if continue_vids %}
<div class="title-bar">
<h1>Continue Watching</h1>
</div>
<div class="video-list {{ view_style }}">
<div class="video-list {{ view_style }} {% if view_style == "grid" %}grid-{{ grid_items }}{% endif %}">
{% for video in continue_vids %}
<div class="video-item {{ view_style }}">
<a href="#player" data-id="{{ video.source.youtube_id }}" onclick="createPlayer(this)">
@ -74,14 +74,21 @@
</div>
<div class="view-icons">
<img src="{% static 'img/icon-sort.svg' %}" alt="sort-icon" onclick="showForm()" id="animate-icon">
{% if view_style == "grid" %}
<div class="grid-count">
<span>{{ grid_items }}</span>
<img src="{% static 'img/icon-add.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"1"}}" alt="grid plus row">
<img src="{% static 'img/icon-substract.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"-1"}}" alt="grid minus row">
</div>
{% endif %}
<img src="{% static 'img/icon-gridview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="grid" alt="grid view">
<img src="{% static 'img/icon-listview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="list" alt="list view">
</div>
</div>
</div>
<div id="player" class="player-wrapper"></div>
<div class="boxed-content">
<div class="video-list {{ view_style }}">
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
<div class="video-list {{ view_style }} {% if view_style == "grid" %}grid-{{ grid_items }}{% endif %}">
{% if results %}
{% for video in results %}
<div class="video-item {{ view_style }}">

View File

@ -63,6 +63,8 @@
</div>
</div>
{% endif %}
</div>
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
<div class="view-controls">
<div class="toggle">
<span>Hide watched videos:</span>
@ -76,14 +78,21 @@
</div>
</div>
<div class="view-icons">
{% if view_style == "grid" %}
<div class="grid-count">
<span>{{ grid_items }}</span>
<img src="{% static 'img/icon-add.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"1"}}" alt="grid plus row">
<img src="{% static 'img/icon-substract.svg' %}" onclick="changeGridItems(this)" data-value="{{ grid_items|add:"-1"}}" alt="grid minus row">
</div>
{% endif %}
<img src="{% static 'img/icon-gridview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="grid" alt="grid view">
<img src="{% static 'img/icon-listview.svg' %}" onclick="changeView(this)" data-origin="home" data-value="list" alt="list view">
</div>
</div>
</div>
<div id="player" class="player-wrapper"></div>
<div class="boxed-content">
<div class="video-list {{ view_style }}">
<div class="boxed-content {% if view_style == "grid" %}boxed-{{ grid_items }}{% endif %}">
<div class="video-list {{ view_style }} {% if view_style == "grid" %}grid-{{ grid_items }}{% endif %}">
{% if results %}
{% for video in results %}
<div class="video-item {{ view_style }}">

View File

@ -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(),

View File

@ -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,

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="474.99609"
height="78.428696"
viewBox="0 0 125.67634 20.750926"
version="1.1"
id="svg1303"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="icon-add.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs1297" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.85859018"
inkscape:cx="-97.380081"
inkscape:cy="261.09215"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<sodipodi:guide
position="-225.18364,87.524084"
orientation="1,0"
id="guide1072"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata1300">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-3.3077777,-220.4781)">
<path
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
d="m 12.291019,220.47796 c -4.9648228,-0.009 -8.9691711,3.98046 -8.9784054,8.94536 l -0.00482,2.62846 c -0.00925,4.9649 3.9804459,8.96933 8.9452674,8.97832 107.703889,0.29274 22.266017,0.0414 107.747629,0.19881 4.96484,0.009 8.96917,-3.98046 8.9784,-8.94534 l 0.005,-2.62847 c 0.009,-4.96489 -3.98037,-8.96923 -8.94525,-8.97831 -107.850822,-0.0255 -20.413204,-0.038 -107.747821,-0.19883 z"
id="rect1073"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -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;