mirror of
https://github.com/tubearchivist/tubearchivist-frontend.git
synced 2024-11-22 11:50:14 +00:00
implement sponsorblock skipping, #build
Changed: - sponsorblock frontent implementation, #208 - per channel sponsorblock - improved scheduler input validation
This commit is contained in:
commit
fd5de99674
@ -86,6 +86,7 @@ class VideoApiView(ApiBaseView):
|
|||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
"""get request"""
|
"""get request"""
|
||||||
self.get_document(video_id)
|
self.get_document(video_id)
|
||||||
|
if self.response.get("data"):
|
||||||
self.process_keys()
|
self.process_keys()
|
||||||
return Response(self.response, status=self.status_code)
|
return Response(self.response, status=self.status_code)
|
||||||
|
|
||||||
|
@ -97,9 +97,8 @@ class StartupCheck:
|
|||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
print(
|
print(
|
||||||
"minial required elasticsearch version: "
|
"required elasticsearch version: "
|
||||||
+ f"{self.MIN_MAJOR}.{self.MIN_MINOR}, "
|
+ f"{self.MIN_MAJOR}.{self.MIN_MINOR}"
|
||||||
+ "please update to recommended version."
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
@ -4,8 +4,10 @@ functionality:
|
|||||||
- check for missing thumbnails
|
- check for missing thumbnails
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
import os
|
import os
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
from io import BytesIO
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -15,7 +17,7 @@ from home.src.ta.config import AppConfig
|
|||||||
from home.src.ta.helper import ignore_filelist
|
from home.src.ta.helper import ignore_filelist
|
||||||
from home.src.ta.ta_redis import RedisArchivist
|
from home.src.ta.ta_redis import RedisArchivist
|
||||||
from mutagen.mp4 import MP4, MP4Cover
|
from mutagen.mp4 import MP4, MP4Cover
|
||||||
from PIL import Image
|
from PIL import Image, ImageFilter
|
||||||
|
|
||||||
|
|
||||||
class ThumbManager:
|
class ThumbManager:
|
||||||
@ -241,6 +243,21 @@ class ThumbManager:
|
|||||||
}
|
}
|
||||||
RedisArchivist().set_message("message:download", mess_dict)
|
RedisArchivist().set_message("message:download", mess_dict)
|
||||||
|
|
||||||
|
def get_base64_blur(self, youtube_id):
|
||||||
|
"""return base64 encoded placeholder"""
|
||||||
|
img_path = self.vid_thumb_path(youtube_id)
|
||||||
|
file_path = os.path.join(self.CACHE_DIR, img_path)
|
||||||
|
img_raw = Image.open(file_path)
|
||||||
|
img_raw.thumbnail((img_raw.width // 20, img_raw.height // 20))
|
||||||
|
img_blur = img_raw.filter(ImageFilter.BLUR)
|
||||||
|
buffer = BytesIO()
|
||||||
|
img_blur.save(buffer, format="JPEG")
|
||||||
|
img_data = buffer.getvalue()
|
||||||
|
img_base64 = base64.b64encode(img_data).decode()
|
||||||
|
data_url = f"data:image/jpg;base64,{img_base64}"
|
||||||
|
|
||||||
|
return data_url
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def vid_thumb_path(youtube_id):
|
def vid_thumb_path(youtube_id):
|
||||||
"""build expected path for video thumbnail from youtube_id"""
|
"""build expected path for video thumbnail from youtube_id"""
|
||||||
|
@ -177,7 +177,9 @@ class VideoDownloader:
|
|||||||
except yt_dlp.utils.DownloadError:
|
except yt_dlp.utils.DownloadError:
|
||||||
print("failed to download " + youtube_id)
|
print("failed to download " + youtube_id)
|
||||||
continue
|
continue
|
||||||
vid_dict = index_new_video(youtube_id)
|
vid_dict = index_new_video(
|
||||||
|
youtube_id, video_overwrites=self.video_overwrites
|
||||||
|
)
|
||||||
self.channels.add(vid_dict["channel"]["channel_id"])
|
self.channels.add(vid_dict["channel"]["channel_id"])
|
||||||
self.move_to_archive(vid_dict)
|
self.move_to_archive(vid_dict)
|
||||||
self._delete_from_pending(youtube_id)
|
self._delete_from_pending(youtube_id)
|
||||||
|
@ -50,6 +50,9 @@
|
|||||||
},
|
},
|
||||||
"index_playlists": {
|
"index_playlists": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"integrate_sponsorblock": {
|
||||||
|
"type" : "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +76,10 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"index": false
|
"index": false
|
||||||
},
|
},
|
||||||
|
"vid_thumb_base64": {
|
||||||
|
"type": "text",
|
||||||
|
"index": false
|
||||||
|
},
|
||||||
"date_downloaded": {
|
"date_downloaded": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
@ -126,6 +133,9 @@
|
|||||||
},
|
},
|
||||||
"index_playlists": {
|
"index_playlists": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"integrate_sponsorblock": {
|
||||||
|
"type" : "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,17 @@ class ChannelOverwriteForm(forms.Form):
|
|||||||
("1", "Enable playlist index"),
|
("1", "Enable playlist index"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
SP_CHOICES = [
|
||||||
|
("", "-- change sponsorblock integrations"),
|
||||||
|
("0", "disable sponsorblock integration"),
|
||||||
|
("1", "enable sponsorblock integration"),
|
||||||
|
]
|
||||||
|
|
||||||
download_format = forms.CharField(label=False, required=False)
|
download_format = forms.CharField(label=False, required=False)
|
||||||
autodelete_days = forms.IntegerField(label=False, required=False)
|
autodelete_days = forms.IntegerField(label=False, required=False)
|
||||||
index_playlists = forms.ChoiceField(
|
index_playlists = forms.ChoiceField(
|
||||||
widget=forms.Select, choices=PLAYLIST_INDEX, required=False
|
widget=forms.Select, choices=PLAYLIST_INDEX, required=False
|
||||||
)
|
)
|
||||||
|
integrate_sponsorblock = forms.ChoiceField(
|
||||||
|
widget=forms.Select, choices=SP_CHOICES, required=False
|
||||||
|
)
|
||||||
|
@ -340,7 +340,12 @@ class YoutubeChannel(YouTubeItem):
|
|||||||
|
|
||||||
def set_overwrites(self, overwrites):
|
def set_overwrites(self, overwrites):
|
||||||
"""set per channel overwrites"""
|
"""set per channel overwrites"""
|
||||||
valid_keys = ["download_format", "autodelete_days", "index_playlists"]
|
valid_keys = [
|
||||||
|
"download_format",
|
||||||
|
"autodelete_days",
|
||||||
|
"index_playlists",
|
||||||
|
"integrate_sponsorblock",
|
||||||
|
]
|
||||||
|
|
||||||
to_write = self.json_data.get("channel_overwrites", {})
|
to_write = self.json_data.get("channel_overwrites", {})
|
||||||
for key, value in overwrites.items():
|
for key, value in overwrites.items():
|
||||||
|
@ -10,6 +10,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from home.src.download.thumbnails import ThumbManager
|
||||||
from home.src.es.connect import ElasticWrap
|
from home.src.es.connect import ElasticWrap
|
||||||
from home.src.index import channel as ta_channel
|
from home.src.index import channel as ta_channel
|
||||||
from home.src.index.generic import YouTubeItem
|
from home.src.index.generic import YouTubeItem
|
||||||
@ -357,9 +358,10 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||||||
index_name = "ta_video"
|
index_name = "ta_video"
|
||||||
yt_base = "https://www.youtube.com/watch?v="
|
yt_base = "https://www.youtube.com/watch?v="
|
||||||
|
|
||||||
def __init__(self, youtube_id):
|
def __init__(self, youtube_id, video_overwrites=False):
|
||||||
super().__init__(youtube_id)
|
super().__init__(youtube_id)
|
||||||
self.channel_id = False
|
self.channel_id = False
|
||||||
|
self.video_overwrites = video_overwrites
|
||||||
self.es_path = f"{self.index_name}/_doc/{youtube_id}"
|
self.es_path = f"{self.index_name}/_doc/{youtube_id}"
|
||||||
|
|
||||||
def build_json(self):
|
def build_json(self):
|
||||||
@ -376,11 +378,24 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||||||
if self.config["downloads"]["integrate_ryd"]:
|
if self.config["downloads"]["integrate_ryd"]:
|
||||||
self._get_ryd_stats()
|
self._get_ryd_stats()
|
||||||
|
|
||||||
if self.config["downloads"]["integrate_sponsorblock"]:
|
if self._check_get_sb():
|
||||||
self._get_sponsorblock()
|
self._get_sponsorblock()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _check_get_sb(self):
|
||||||
|
"""check if need to run sponsor block"""
|
||||||
|
if self.config["downloads"]["integrate_sponsorblock"]:
|
||||||
|
return True
|
||||||
|
try:
|
||||||
|
single_overwrite = self.video_overwrites[self.youtube_id]
|
||||||
|
_ = single_overwrite["integrate_sponsorblock"]
|
||||||
|
return True
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def _process_youtube_meta(self):
|
def _process_youtube_meta(self):
|
||||||
"""extract relevant fields from youtube"""
|
"""extract relevant fields from youtube"""
|
||||||
# extract
|
# extract
|
||||||
@ -389,12 +404,14 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||||||
upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
|
upload_date_time = datetime.strptime(upload_date, "%Y%m%d")
|
||||||
published = upload_date_time.strftime("%Y-%m-%d")
|
published = upload_date_time.strftime("%Y-%m-%d")
|
||||||
last_refresh = int(datetime.now().strftime("%s"))
|
last_refresh = int(datetime.now().strftime("%s"))
|
||||||
|
base64_blur = ThumbManager().get_base64_blur(self.youtube_id)
|
||||||
# build json_data basics
|
# build json_data basics
|
||||||
self.json_data = {
|
self.json_data = {
|
||||||
"title": self.youtube_meta["title"],
|
"title": self.youtube_meta["title"],
|
||||||
"description": self.youtube_meta["description"],
|
"description": self.youtube_meta["description"],
|
||||||
"category": self.youtube_meta["categories"],
|
"category": self.youtube_meta["categories"],
|
||||||
"vid_thumb_url": self.youtube_meta["thumbnail"],
|
"vid_thumb_url": self.youtube_meta["thumbnail"],
|
||||||
|
"vid_thumb_base64": base64_blur,
|
||||||
"tags": self.youtube_meta["tags"],
|
"tags": self.youtube_meta["tags"],
|
||||||
"published": published,
|
"published": published,
|
||||||
"vid_last_refresh": last_refresh,
|
"vid_last_refresh": last_refresh,
|
||||||
@ -495,7 +512,10 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||||||
|
|
||||||
for media_url in to_del:
|
for media_url in to_del:
|
||||||
file_path = os.path.join(video_base, media_url)
|
file_path = os.path.join(video_base, media_url)
|
||||||
|
try:
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"{self.youtube_id}: failed {media_url}, continue.")
|
||||||
|
|
||||||
self.del_in_es()
|
self.del_in_es()
|
||||||
self.delete_subtitles()
|
self.delete_subtitles()
|
||||||
@ -541,9 +561,9 @@ class YoutubeVideo(YouTubeItem, YoutubeSubtitle):
|
|||||||
_, _ = ElasticWrap(path).post(data=data)
|
_, _ = ElasticWrap(path).post(data=data)
|
||||||
|
|
||||||
|
|
||||||
def index_new_video(youtube_id):
|
def index_new_video(youtube_id, video_overwrites=False):
|
||||||
"""combined classes to create new video in index"""
|
"""combined classes to create new video in index"""
|
||||||
video = YoutubeVideo(youtube_id)
|
video = YoutubeVideo(youtube_id, video_overwrites=video_overwrites)
|
||||||
video.build_json()
|
video.build_json()
|
||||||
if not video.json_data:
|
if not video.json_data:
|
||||||
raise ValueError("failed to get metadata for " + youtube_id)
|
raise ValueError("failed to get metadata for " + youtube_id)
|
||||||
|
@ -83,31 +83,30 @@ class AppConfig:
|
|||||||
|
|
||||||
def update_config(self, form_post):
|
def update_config(self, form_post):
|
||||||
"""update config values from settings form"""
|
"""update config values from settings form"""
|
||||||
config = self.config
|
|
||||||
for key, value in form_post.items():
|
for key, value in form_post.items():
|
||||||
to_write = value[0]
|
if not value and not isinstance(value, int):
|
||||||
if len(to_write):
|
continue
|
||||||
if to_write == "0":
|
|
||||||
|
if value in ["0", 0]:
|
||||||
to_write = False
|
to_write = False
|
||||||
elif to_write == "1":
|
elif value == "1":
|
||||||
to_write = True
|
to_write = True
|
||||||
elif to_write.isdigit():
|
else:
|
||||||
to_write = int(to_write)
|
to_write = value
|
||||||
|
|
||||||
config_dict, config_value = key.split("_", maxsplit=1)
|
config_dict, config_value = key.split("_", maxsplit=1)
|
||||||
config[config_dict][config_value] = to_write
|
self.config[config_dict][config_value] = to_write
|
||||||
|
|
||||||
RedisArchivist().set_message("config", config, expire=False)
|
RedisArchivist().set_message("config", self.config, expire=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_user_config(form_post, user_id):
|
def set_user_config(form_post, user_id):
|
||||||
"""set values in redis for user settings"""
|
"""set values in redis for user settings"""
|
||||||
for key, value in form_post.items():
|
for key, value in form_post.items():
|
||||||
to_write = value[0]
|
if not value:
|
||||||
if len(to_write):
|
continue
|
||||||
if to_write.isdigit():
|
|
||||||
to_write = int(to_write)
|
message = {"status": value}
|
||||||
message = {"status": to_write}
|
|
||||||
redis_key = f"{user_id}:{key}"
|
redis_key = f"{user_id}:{key}"
|
||||||
RedisArchivist().set_message(redis_key, message, expire=False)
|
RedisArchivist().set_message(redis_key, message, expire=False)
|
||||||
|
|
||||||
@ -172,12 +171,11 @@ class ScheduleBuilder:
|
|||||||
print("processing form, restart container for changes to take effect")
|
print("processing form, restart container for changes to take effect")
|
||||||
redis_config = self.config
|
redis_config = self.config
|
||||||
for key, value in form_post.items():
|
for key, value in form_post.items():
|
||||||
to_check = value[0]
|
if key in self.SCHEDULES and value:
|
||||||
if key in self.SCHEDULES and to_check:
|
|
||||||
try:
|
try:
|
||||||
to_write = self.value_builder(key, to_check)
|
to_write = self.value_builder(key, value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print(f"failed: {key} {to_check}")
|
print(f"failed: {key} {value}")
|
||||||
mess_dict = {
|
mess_dict = {
|
||||||
"status": "message:setting",
|
"status": "message:setting",
|
||||||
"level": "error",
|
"level": "error",
|
||||||
@ -188,8 +186,8 @@ class ScheduleBuilder:
|
|||||||
return
|
return
|
||||||
|
|
||||||
redis_config["scheduler"][key] = to_write
|
redis_config["scheduler"][key] = to_write
|
||||||
if key in self.CONFIG and to_check:
|
if key in self.CONFIG and value:
|
||||||
redis_config["scheduler"][key] = int(to_check)
|
redis_config["scheduler"][key] = int(value)
|
||||||
RedisArchivist().set_message("config", redis_config, expire=False)
|
RedisArchivist().set_message("config", redis_config, expire=False)
|
||||||
mess_dict = {
|
mess_dict = {
|
||||||
"status": "message:setting",
|
"status": "message:setting",
|
||||||
@ -199,37 +197,56 @@ class ScheduleBuilder:
|
|||||||
}
|
}
|
||||||
RedisArchivist().set_message("message:setting", mess_dict)
|
RedisArchivist().set_message("message:setting", mess_dict)
|
||||||
|
|
||||||
def value_builder(self, key, to_check):
|
def value_builder(self, key, value):
|
||||||
"""validate single cron form entry and return cron dict"""
|
"""validate single cron form entry and return cron dict"""
|
||||||
print(f"change schedule for {key} to {to_check}")
|
print(f"change schedule for {key} to {value}")
|
||||||
if to_check == "0":
|
if value == "0":
|
||||||
# deactivate this schedule
|
# deactivate this schedule
|
||||||
return False
|
return False
|
||||||
if re.search(r"[\d]{1,2}\/[\d]{1,2}", to_check):
|
if re.search(r"[\d]{1,2}\/[\d]{1,2}", value):
|
||||||
# number/number cron format will fail in celery
|
# number/number cron format will fail in celery
|
||||||
print("number/number schedule formatting not supported")
|
print("number/number schedule formatting not supported")
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
keys = ["minute", "hour", "day_of_week"]
|
keys = ["minute", "hour", "day_of_week"]
|
||||||
if to_check == "auto":
|
if value == "auto":
|
||||||
# set to sensible default
|
# set to sensible default
|
||||||
values = self.SCHEDULES[key].split()
|
values = self.SCHEDULES[key].split()
|
||||||
else:
|
else:
|
||||||
values = to_check.split()
|
values = value.split()
|
||||||
|
|
||||||
if len(keys) != len(values):
|
if len(keys) != len(values):
|
||||||
print(f"failed to parse {to_check} for {key}")
|
print(f"failed to parse {value} for {key}")
|
||||||
raise ValueError("invalid input")
|
raise ValueError("invalid input")
|
||||||
|
|
||||||
to_write = dict(zip(keys, values))
|
to_write = dict(zip(keys, values))
|
||||||
try:
|
self._validate_cron(to_write)
|
||||||
int(to_write["minute"])
|
|
||||||
except ValueError as error:
|
|
||||||
print("too frequent: only number in minutes are supported")
|
|
||||||
raise ValueError("invalid input") from error
|
|
||||||
|
|
||||||
return to_write
|
return to_write
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_cron(to_write):
|
||||||
|
"""validate all fields, raise value error for impossible schedule"""
|
||||||
|
all_hours = list(re.split(r"\D+", to_write["hour"]))
|
||||||
|
for hour in all_hours:
|
||||||
|
if hour.isdigit() and int(hour) > 23:
|
||||||
|
print("hour can not be greater than 23")
|
||||||
|
raise ValueError("invalid input")
|
||||||
|
|
||||||
|
all_days = list(re.split(r"\D+", to_write["day_of_week"]))
|
||||||
|
for day in all_days:
|
||||||
|
if day.isdigit() and int(day) > 6:
|
||||||
|
print("day can not be greater than 6")
|
||||||
|
raise ValueError("invalid input")
|
||||||
|
|
||||||
|
if not to_write["minute"].isdigit():
|
||||||
|
print("too frequent: only number in minutes are supported")
|
||||||
|
raise ValueError("invalid input")
|
||||||
|
|
||||||
|
if int(to_write["minute"]) > 59:
|
||||||
|
print("minutes can not be greater than 59")
|
||||||
|
raise ValueError("invalid input")
|
||||||
|
|
||||||
def build_schedule(self):
|
def build_schedule(self):
|
||||||
"""build schedule dict as expected by app.conf.beat_schedule"""
|
"""build schedule dict as expected by app.conf.beat_schedule"""
|
||||||
schedule_dict = {}
|
schedule_dict = {}
|
||||||
|
@ -89,7 +89,15 @@
|
|||||||
{% endif %}</span></p>
|
{% endif %}</span></p>
|
||||||
{{ channel_overwrite_form.index_playlists }}<br>
|
{{ channel_overwrite_form.index_playlists }}<br>
|
||||||
</div>
|
</div>
|
||||||
<div class="overwrite-form-item"></div>
|
<div class="overwrite-form-item">
|
||||||
|
<p>Enable <a href="https://sponsor.ajay.app/" target="_blank">SponsorBlock</a>: <span class="settings-current">
|
||||||
|
{% if channel_info.channel_overwrites.integrate_sponsorblock %}
|
||||||
|
{{ channel_info.channel_overwrites.integrate_sponsorblock }}
|
||||||
|
{% else %}
|
||||||
|
False
|
||||||
|
{% endif %}</span></p>
|
||||||
|
{{ channel_overwrite_form.integrate_sponsorblock }}<br>
|
||||||
|
</div>
|
||||||
<button type="submit">Save Channel Overwrites</button>
|
<button type="submit">Save Channel Overwrites</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -124,12 +124,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<p>Integrate with <a href="https://returnyoutubedislike.com/">returnyoutubedislike.com</a> to get dislikes and average ratings back: <span class="settings-current">{{ config.downloads.integrate_ryd }}</span></p>
|
<p>Integrate with <a href="https://returnyoutubedislike.com/" target="_blank">returnyoutubedislike.com</a> to get dislikes and average ratings back: <span class="settings-current">{{ config.downloads.integrate_ryd }}</span></p>
|
||||||
<i>Before activating that, make sure you have a scraping sleep interval of at least 3 secs set to avoid ratelimiting issues.</i><br>
|
<i>Before activating that, make sure you have a scraping sleep interval of at least 3 secs set to avoid ratelimiting issues.</i><br>
|
||||||
{{ app_form.downloads_integrate_ryd }}
|
{{ app_form.downloads_integrate_ryd }}
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-item">
|
<div class="settings-item">
|
||||||
<p>Integrate with <a href="https://sponsor.ajay.app/">SponsorBlock</a> to get sponsored timestamps: <span class="settings-current">{{ config.downloads.integrate_sponsorblock }}</span></p>
|
<p>Integrate with <a href="https://sponsor.ajay.app/" target="_blank">SponsorBlock</a> to get sponsored timestamps: <span class="settings-current">{{ config.downloads.integrate_sponsorblock }}</span></p>
|
||||||
<i>Before activating that, make sure you have a scraping sleep interval of at least 3 secs set to avoid ratelimiting issues.</i><br>
|
<i>Before activating that, make sure you have a scraping sleep interval of at least 3 secs set to avoid ratelimiting issues.</i><br>
|
||||||
{{ app_form.downloads_integrate_sponsorblock }}
|
{{ app_form.downloads_integrate_sponsorblock }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,36 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
<div class="video-main"></div>
|
<div class="video-main"></div>
|
||||||
|
<div class="notifications" id="notifications"></div>
|
||||||
|
<div class="sponsorblock" id="sponsorblock">
|
||||||
|
{% if video.channel.channel_overwrites.integrate_sponsorblock %}
|
||||||
|
{% if video.channel.channel_overwrites.integrate_sponsorblock == True %}
|
||||||
|
{% if not video.sponsorblock %}
|
||||||
|
<h4>This video doesn't have any sponsor segments added. To add a segment go to <u><a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">this video on YouTube</a></u> and add a segment using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% if video.sponsorblock %}
|
||||||
|
{% for segment in video.sponsorblock %}
|
||||||
|
{% if segment.locked != 1 %}
|
||||||
|
<h4>This video has unlocked sponsor segments. Go to <u><a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">this video on YouTube</a></u> and vote on the segments using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
{{ break }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% elif config.downloads.integrate_sponsorblock %}
|
||||||
|
{% if not video.sponsorblock %}
|
||||||
|
<h4>This video doesn't have any sponsor segments added. To add a segment go to <u><a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">this video on YouTube</a></u> and add a segment using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
{% endif %}
|
||||||
|
{% if video.sponsorblock %}
|
||||||
|
{% for segment in video.sponsorblock %}
|
||||||
|
{% if segment.locked != 1 %}
|
||||||
|
<h4>This video has unlocked sponsor segments. Go to <u><a href="https://www.youtube.com/watch?v={{ video.youtube_id }}">this video on YouTube</a></u> and vote on the segments using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
{{ break }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="boxed-content">
|
<div class="boxed-content">
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
{% if cast %}
|
{% if cast %}
|
||||||
@ -114,6 +144,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var videoData = getVideoData('{{ video.youtube_id }}');
|
var videoData = getVideoData('{{ video.youtube_id }}');
|
||||||
|
sponsorBlock = videoData.data.sponsorblock;
|
||||||
var videoProgress = getVideoProgress('{{ video.youtube_id }}').position;
|
var videoProgress = getVideoProgress('{{ video.youtube_id }}').position;
|
||||||
window.onload = insertVideoTag(videoData, videoProgress);
|
window.onload = insertVideoTag(videoData, videoProgress);
|
||||||
</script>
|
</script>
|
||||||
|
@ -8,7 +8,6 @@ import json
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
@ -112,7 +111,7 @@ class ArchivistViewConfig(View):
|
|||||||
"""build default context for every view"""
|
"""build default context for every view"""
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.user_conf = RedisArchivist()
|
self.user_conf = RedisArchivist()
|
||||||
self.default_conf = AppConfig().config
|
self.default_conf = AppConfig(self.user_id).config
|
||||||
|
|
||||||
self.context = {
|
self.context = {
|
||||||
"colors": self.default_conf["application"]["colors"],
|
"colors": self.default_conf["application"]["colors"],
|
||||||
@ -671,7 +670,7 @@ class VideoView(View):
|
|||||||
|
|
||||||
def get(self, request, video_id):
|
def get(self, request, video_id):
|
||||||
"""get single video"""
|
"""get single video"""
|
||||||
colors, cast = self.read_config(user_id=request.user.id)
|
config_handler = AppConfig(request.user.id)
|
||||||
path = f"ta_video/_doc/{video_id}"
|
path = f"ta_video/_doc/{video_id}"
|
||||||
look_up = SearchHandler(path, config=False)
|
look_up = SearchHandler(path, config=False)
|
||||||
video_hit = look_up.get_data()
|
video_hit = look_up.get_data()
|
||||||
@ -693,9 +692,10 @@ class VideoView(View):
|
|||||||
"video": video_data,
|
"video": video_data,
|
||||||
"playlist_nav": playlist_nav,
|
"playlist_nav": playlist_nav,
|
||||||
"title": video_title,
|
"title": video_title,
|
||||||
"colors": colors,
|
"colors": config_handler.colors,
|
||||||
"cast": cast,
|
"cast": config_handler.config["application"]["enable_cast"],
|
||||||
"version": settings.TA_VERSION,
|
"version": settings.TA_VERSION,
|
||||||
|
"config": config_handler.config,
|
||||||
}
|
}
|
||||||
return render(request, "home/video.html", context)
|
return render(request, "home/video.html", context)
|
||||||
|
|
||||||
@ -712,14 +712,6 @@ class VideoView(View):
|
|||||||
|
|
||||||
return all_navs
|
return all_navs
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def read_config(user_id):
|
|
||||||
"""read config file"""
|
|
||||||
config_handler = AppConfig(user_id)
|
|
||||||
cast = config_handler.config["application"]["enable_cast"]
|
|
||||||
colors = config_handler.colors
|
|
||||||
return colors, cast
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def star_creator(rating):
|
def star_creator(rating):
|
||||||
"""convert rating float to stars"""
|
"""convert rating float to stars"""
|
||||||
@ -802,23 +794,25 @@ class SettingsView(View):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def post(request):
|
def post(request):
|
||||||
"""handle form post to update settings"""
|
"""handle form post to update settings"""
|
||||||
|
user_form = UserSettingsForm(request.POST)
|
||||||
|
if user_form.is_valid():
|
||||||
|
user_form_post = user_form.cleaned_data
|
||||||
|
if any(user_form_post.values()):
|
||||||
|
AppConfig().set_user_config(user_form_post, request.user.id)
|
||||||
|
|
||||||
form_response = forms.Form(request.POST)
|
app_form = ApplicationSettingsForm(request.POST)
|
||||||
if form_response.is_valid():
|
if app_form.is_valid():
|
||||||
form_post = dict(request.POST)
|
app_form_post = app_form.cleaned_data
|
||||||
print(form_post)
|
if app_form_post:
|
||||||
del form_post["csrfmiddlewaretoken"]
|
print(app_form_post)
|
||||||
config_handler = AppConfig()
|
AppConfig().update_config(app_form_post)
|
||||||
if "application-settings" in form_post:
|
|
||||||
del form_post["application-settings"]
|
scheduler_form = SchedulerSettingsForm(request.POST)
|
||||||
config_handler.update_config(form_post)
|
if scheduler_form.is_valid():
|
||||||
elif "user-settings" in form_post:
|
scheduler_form_post = scheduler_form.cleaned_data
|
||||||
del form_post["user-settings"]
|
if any(scheduler_form_post.values()):
|
||||||
config_handler.set_user_config(form_post, request.user.id)
|
print(scheduler_form_post)
|
||||||
elif "scheduler-settings" in form_post:
|
ScheduleBuilder().update_schedule_conf(scheduler_form_post)
|
||||||
del form_post["scheduler-settings"]
|
|
||||||
print(form_post)
|
|
||||||
ScheduleBuilder().update_schedule_conf(form_post)
|
|
||||||
|
|
||||||
sleep(1)
|
sleep(1)
|
||||||
return redirect("settings", permanent=True)
|
return redirect("settings", permanent=True)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
beautifulsoup4==4.10.0
|
beautifulsoup4==4.11.1
|
||||||
celery==5.2.6
|
celery==5.2.6
|
||||||
Django==4.0.3
|
Django==4.0.3
|
||||||
django-cors-headers==3.11.0
|
django-cors-headers==3.11.0
|
||||||
@ -9,4 +9,4 @@ requests==2.27.1
|
|||||||
ryd-client==0.0.3
|
ryd-client==0.0.3
|
||||||
uWSGI==2.0.20
|
uWSGI==2.0.20
|
||||||
whitenoise==6.0.0
|
whitenoise==6.0.0
|
||||||
yt_dlp==2022.3.8.2
|
yt_dlp==2022.4.8
|
||||||
|
@ -62,6 +62,13 @@ h3 {
|
|||||||
color: var(--accent-font-light);
|
color: var(--accent-font-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
font-family: Sen-Regular, sans-serif;
|
||||||
|
color: var(--accent-font-light);
|
||||||
|
}
|
||||||
|
|
||||||
p, i, li {
|
p, i, li {
|
||||||
font-family: Sen-Regular, sans-serif;
|
font-family: Sen-Regular, sans-serif;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
@ -355,6 +362,18 @@ button:hover {
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notifications {
|
||||||
|
text-align: center;
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsorblock {
|
||||||
|
text-align: center;
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.video-player video,
|
.video-player video,
|
||||||
.video-main video {
|
.video-main video {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
@ -327,9 +327,33 @@ function cancelDelete() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// player
|
// player
|
||||||
|
var sponsorBlock = [];
|
||||||
function createPlayer(button) {
|
function createPlayer(button) {
|
||||||
var videoId = button.getAttribute('data-id');
|
var videoId = button.getAttribute('data-id');
|
||||||
var videoData = getVideoData(videoId);
|
var videoData = getVideoData(videoId);
|
||||||
|
|
||||||
|
var sponsorBlockElements = '';
|
||||||
|
if (videoData.config.downloads.integrate_sponsorblock && (typeof(videoData.data.channel.channel_overwrites) == "undefined" || typeof(videoData.data.channel.channel_overwrites.integrate_sponsorblock) == "undefined" || videoData.data.channel.channel_overwrites.integrate_sponsorblock == true)) {
|
||||||
|
sponsorBlock = videoData.data.sponsorblock;
|
||||||
|
if (!sponsorBlock) {
|
||||||
|
sponsorBlockElements = `
|
||||||
|
<div class="sponsorblock" id="sponsorblock">
|
||||||
|
<h4>This video doesn't have any sponsor segments added. To add a segment go to <u><a href="https://www.youtube.com/watch?v=${videoId}">this video on Youtube</a></u> and add a segment using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
for(let i in sponsorBlock) {
|
||||||
|
if(sponsorBlock[i].locked != 1) {
|
||||||
|
sponsorBlockElements = `
|
||||||
|
<div class="sponsorblock" id="sponsorblock">
|
||||||
|
<h4>This video has unlocked sponsor segments. Go to <u><a href="https://www.youtube.com/watch?v=${videoId}">this video on YouTube</a></u> and vote on the segments using the <u><a href="https://sponsor.ajay.app/">SponsorBlock</a></u> extension.</h4>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var videoProgress = getVideoProgress(videoId).position;
|
var videoProgress = getVideoProgress(videoId).position;
|
||||||
var videoName = videoData.data.title;
|
var videoName = videoData.data.title;
|
||||||
|
|
||||||
@ -353,7 +377,6 @@ function createPlayer(button) {
|
|||||||
var channelName = videoData.data.channel.channel_name;
|
var channelName = videoData.data.channel.channel_name;
|
||||||
|
|
||||||
removePlayer();
|
removePlayer();
|
||||||
// document.getElementById(videoId).outerHTML = ''; // Remove watch indicator from video info
|
|
||||||
|
|
||||||
// If cast integration is enabled create cast button
|
// If cast integration is enabled create cast button
|
||||||
var castButton = '';
|
var castButton = '';
|
||||||
@ -383,6 +406,8 @@ function createPlayer(button) {
|
|||||||
const markup = `
|
const markup = `
|
||||||
<div class="video-player" data-id="${videoId}">
|
<div class="video-player" data-id="${videoId}">
|
||||||
${videoTag}
|
${videoTag}
|
||||||
|
<div class="notifications" id="notifications"></div>
|
||||||
|
${sponsorBlockElements}
|
||||||
<div class="player-title boxed-content">
|
<div class="player-title boxed-content">
|
||||||
<img class="close-button" src="/static/img/icon-close.svg" alt="close-icon" data="${videoId}" onclick="removePlayer()" title="Close player">
|
<img class="close-button" src="/static/img/icon-close.svg" alt="close-icon" data="${videoId}" onclick="removePlayer()" title="Close player">
|
||||||
${watchStatusIndicator}
|
${watchStatusIndicator}
|
||||||
@ -400,6 +425,53 @@ function createPlayer(button) {
|
|||||||
divPlayer.innerHTML = markup;
|
divPlayer.innerHTML = markup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function sendSponsorBlockVote(uuid, vote) {
|
||||||
|
// var videoId = getVideoPlayerVideoId();
|
||||||
|
// postSponsorSegmentVote(videoId, uuid, vote);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var sponsorBlockTimestamps = [];
|
||||||
|
// function sendSponsorBlockSegment() {
|
||||||
|
// var videoId = getVideoPlayerVideoId();
|
||||||
|
// var currentTime = getVideoPlayerCurrentTime();
|
||||||
|
// var sponsorBlockElement = document.getElementById("sponsorblock");
|
||||||
|
// if (sponsorBlockTimestamps[1]) {
|
||||||
|
// if (sponsorBlockTimestamps[1] > sponsorBlockTimestamps[0]) {
|
||||||
|
// postSponsorSegment(videoId, sponsorBlockTimestamps[0], sponsorBlockTimestamps[1]);
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <p>Timestamps sent! (Not really)</p>
|
||||||
|
// `;
|
||||||
|
// setTimeout(function(){
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <img src="/static/img/PlayerStartIconSponsorBlocker.svg" class="sponsorblockIcon"><button onclick="sendSponsorBlockSegment()">Start</button>
|
||||||
|
// `;
|
||||||
|
// }, 3000);
|
||||||
|
// } else {
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <span class="danger-zone">Invalid Timestamps!</span>
|
||||||
|
// `;
|
||||||
|
// setTimeout(function(){
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <img src="/static/img/PlayerStartIconSponsorBlocker.svg" class="sponsorblockIcon"><button onclick="sendSponsorBlockSegment()">Start</button>
|
||||||
|
// `;
|
||||||
|
// }, 3000);
|
||||||
|
// }
|
||||||
|
// sponsorBlockTimestamps = [];
|
||||||
|
// } else if (sponsorBlockTimestamps[0]) {
|
||||||
|
// sponsorBlockTimestamps.push(currentTime);
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <img src="/static/img/PlayerStartIconSponsorBlocker.svg" class="sponsorblockIcon" onclick="getVideoPlayer().currentTime = '${sponsorBlockTimestamps[0]}'"><p>${sponsorBlockTimestamps[0].toFixed(1)} s | </p>
|
||||||
|
// <img src="/static/img/PlayerStopIconSponsorBlocker.svg" class="sponsorblockIcon" onclick="getVideoPlayer().currentTime = '${sponsorBlockTimestamps[1]}'"><p>${sponsorBlockTimestamps[1].toFixed(1)} s | </p>
|
||||||
|
// <img src="/static/img/PlayerUploadIconSponsorBlocker.svg" class="sponsorblockIcon"><button onclick="sendSponsorBlockSegment()">Confirm</button>
|
||||||
|
// `;
|
||||||
|
// } else {
|
||||||
|
// sponsorBlockTimestamps.push(currentTime);
|
||||||
|
// sponsorBlockElement.innerHTML = `
|
||||||
|
// <img src="/static/img/PlayerStopIconSponsorBlocker.svg" class="sponsorblockIcon"><button onclick="sendSponsorBlockSegment()">End</button>
|
||||||
|
// `;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)`
|
// Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)`
|
||||||
function insertVideoTag(videoData, videoProgress) {
|
function insertVideoTag(videoData, videoProgress) {
|
||||||
var videoTag = createVideoTag(videoData, videoProgress);
|
var videoTag = createVideoTag(videoData, videoProgress);
|
||||||
@ -488,6 +560,32 @@ function onVideoProgress() {
|
|||||||
var videoId = getVideoPlayerVideoId();
|
var videoId = getVideoPlayerVideoId();
|
||||||
var currentTime = getVideoPlayerCurrentTime();
|
var currentTime = getVideoPlayerCurrentTime();
|
||||||
var duration = getVideoPlayerDuration();
|
var duration = getVideoPlayerDuration();
|
||||||
|
var videoElement = getVideoPlayer();
|
||||||
|
// var sponsorBlockElement = document.getElementById("sponsorblock");
|
||||||
|
var notificationsElement = document.getElementById("notifications");
|
||||||
|
if (sponsorBlock) {
|
||||||
|
for(let i in sponsorBlock) {
|
||||||
|
if(sponsorBlock[i].segment[0] <= currentTime + 0.3 && sponsorBlock[i].segment[0] >= currentTime) {
|
||||||
|
videoElement.currentTime = sponsorBlock[i].segment[1];
|
||||||
|
notificationsElement.innerHTML += `<h3 id="notification-${sponsorBlock[i].UUID}">Skipped sponsor segment from ${formatTime(sponsorBlock[i].segment[0])} to ${formatTime(sponsorBlock[i].segment[1])}.</h3>`;
|
||||||
|
}
|
||||||
|
// if(currentTime >= sponsorBlock[i].segment[1] && currentTime <= sponsorBlock[i].segment[1] + 0.2) {
|
||||||
|
// if(sponsorBlock[i].locked != 1) {
|
||||||
|
// sponsorBlockElement.innerHTML += `
|
||||||
|
// <div id="${sponsorBlock[i].UUID}">
|
||||||
|
// <button onclick="sendSponsorBlockVote('${sponsorBlock[i].UUID}', 1)">Up Vote</button>
|
||||||
|
// <button onclick="sendSponsorBlockVote('${sponsorBlock[i].UUID}', -1)">Down Vote</button>
|
||||||
|
// </div>`;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if(currentTime > sponsorBlock[i].segment[1] + 10) {
|
||||||
|
var notificationsElementUUID = document.getElementById("notification-" + sponsorBlock[i].UUID);
|
||||||
|
if(notificationsElementUUID) {
|
||||||
|
notificationsElementUUID.outerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if ((currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second
|
if ((currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second
|
||||||
postVideoProgress(videoId, currentTime);
|
postVideoProgress(videoId, currentTime);
|
||||||
if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched
|
if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched
|
||||||
@ -542,6 +640,32 @@ function formatNumbers(number) {
|
|||||||
return numberFormatted;
|
return numberFormatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Formats times in seconds for frontend
|
||||||
|
function formatTime(time) {
|
||||||
|
var hoursUnformatted = time / 3600;
|
||||||
|
var minutesUnformatted = (time % 3600) / 60;
|
||||||
|
var secondsUnformatted = time % 60;
|
||||||
|
|
||||||
|
var hoursFormatted = Math.trunc(hoursUnformatted);
|
||||||
|
if(minutesUnformatted < 10 && hoursFormatted > 0) {
|
||||||
|
var minutesFormatted = "0" + Math.trunc(minutesUnformatted);
|
||||||
|
} else {
|
||||||
|
var minutesFormatted = Math.trunc(minutesUnformatted);
|
||||||
|
}
|
||||||
|
if(secondsUnformatted < 10) {
|
||||||
|
var secondsFormatted = "0" + Math.trunc(secondsUnformatted);
|
||||||
|
} else {
|
||||||
|
var secondsFormatted = Math.trunc(secondsUnformatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeUnformatted = '';
|
||||||
|
if(hoursFormatted > 0) {
|
||||||
|
timeUnformatted = hoursFormatted + ":"
|
||||||
|
}
|
||||||
|
var timeFormatted = timeUnformatted.concat(minutesFormatted, ":", secondsFormatted);
|
||||||
|
return timeFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
// Gets video data when passed video ID
|
// Gets video data when passed video ID
|
||||||
function getVideoData(videoId) {
|
function getVideoData(videoId) {
|
||||||
var apiEndpoint = "/api/video/" + videoId + "/";
|
var apiEndpoint = "/api/video/" + videoId + "/";
|
||||||
@ -599,6 +723,30 @@ function postVideoProgress(videoId, videoProgress) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send sponsor segment when given video id and and timestamps
|
||||||
|
function postSponsorSegment(videoId, startTime, endTime) {
|
||||||
|
var apiEndpoint = "/api/video/" + videoId + "/sponsor/";
|
||||||
|
var data = {
|
||||||
|
"segment": {
|
||||||
|
"startTime": startTime,
|
||||||
|
"endTime": endTime
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apiRequest(apiEndpoint, "POST", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sponsor segment when given video id and and timestamps
|
||||||
|
function postSponsorSegmentVote(videoId, uuid, vote) {
|
||||||
|
var apiEndpoint = "/api/video/" + videoId + "/sponsor/";
|
||||||
|
var data = {
|
||||||
|
"vote": {
|
||||||
|
"uuid": uuid,
|
||||||
|
"yourVote": vote
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apiRequest(apiEndpoint, "POST", data);
|
||||||
|
}
|
||||||
|
|
||||||
// Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE")
|
// Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE")
|
||||||
function apiRequest(apiEndpoint, method, data) {
|
function apiRequest(apiEndpoint, method, data) {
|
||||||
const xhttp = new XMLHttpRequest();
|
const xhttp = new XMLHttpRequest();
|
||||||
|
Loading…
Reference in New Issue
Block a user