Open
Description
I've read the documentation
- I have read the how to open an issue guide, particularly the feature request section.
Your Feature Request
I would like comments to have clickable timestamps i.e if the text 12:13
is found in the comment it should be turned into a clickable link that would take me to the same timestamp in the videoplayer.
Your help is needed!
- Yes I will work on this in the next few days or weeks.
Example implementation:
tubearchivist/api/views.py
diff --git a/tubearchivist/api/views.py b/tubearchivist/api/views.py
index e026dba..e1f8636 100644
--- a/tubearchivist/api/views.py
+++ b/tubearchivist/api/views.py
@@ -50,6 +50,11 @@ from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.response import Response
from rest_framework.views import APIView
+import re
+import logging
+import time
+
+logger = logging.getLogger('ta')
def check_admin(user):
"""check for admin permission for restricted views"""
@@ -230,8 +235,39 @@ class VideoCommentView(ApiBaseView):
# pylint: disable=unused-argument
self.get_document(video_id)
+ # convert \n to <br> for comments and replies
+ data = self.response.get("data")
+ if isinstance(data, list):
+ comment_count = 0
+ start_time = time.time() # Start the timer
+ for comment in data:
+ comment_count += 1
+ comment["comment_text"] = comment["comment_text"].replace("\n", "<br>")
+ comment["comment_text"] = self._add_seek_to(comment["comment_text"])
+ for reply in comment["comment_replies"]:
+ comment_count += 1
+ reply["comment_text"] = reply["comment_text"].replace("\n", "<br>")
+ reply["comment_text"] = self._add_seek_to(reply["comment_text"])
+ end_time = time.time() # End the timer
+ logger.debug("Time taken to parse %s comments: %s seconds", comment_count, (end_time - start_time))
+ else:
+ logger.warning("No comments found for video %s", video_id)
+
return Response(self.response, status=self.status_code)
+ def _add_seek_to(self, comment_text):
+ """add js seekTo for timestamp in comments """
+
+ # Regular expression to match timestamps in the format of HH:MM:SS or MM:SS
+ timestamp_pattern = r"\b(?:[0-1]?[0-9]|2[0-3]):(?:[0-5]?[0-9]):(?:[0-5][0-9])|\b(?:[0-5]?[0-9]):(?:[0-5][0-9])\b"
+ timestamps = re.findall(timestamp_pattern, comment_text)
+
+ for timestamp in timestamps:
+ seek_to = f"javascript:seekVideoTo('{timestamp}')"
+ comment_text = comment_text.replace(timestamp, f'<a href="{seek_to}" class="ta-comment-timesamp>{timestamp}</a>')
+
+ return comment_text
+
class VideoSimilarView(ApiBaseView):
"""resolves to /api/video/<video-id>/similar/
tubearchivist/static/script.js
diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js
index b1e6ad1..2423fbb 100644
--- a/tubearchivist/static/script.js
+++ b/tubearchivist/static/script.js
@@ -1266,7 +1266,7 @@ function createCommentBox(comment, isRoot) {
commentBox.appendChild(commentAuthor);
let commentText = document.createElement('p');
- commentText.innerText = comment.comment_text;
+ commentText.innerHTML = comment.comment_text;
commentBox.appendChild(commentText);
const spacer = '<span class="space-carrot">|</span>';
@@ -1567,3 +1567,21 @@ function doShortcut(e) {
}
}
}
+
+function seekVideoTo(timestamp) {
+ let videoElement = getVideoPlayer();
+ if (videoElement != null) {
+ const parts = timestamp.split(':');
+ // HH:MM:SS and MM:SS
+ let seconds;
+ if (parts.length === 3) {
+ seconds = parseInt(parts[0]) * 3600 + parseInt(parts[1]) * 60 + parseInt(parts[2]);
+ } else {
+ seconds = parseInt(parts[0]) * 60 + parseInt(parts[1]);
+ }
+ videoElement.currentTime = seconds;
+
+ videoElement.scrollIntoView({ behavior: 'smooth' });
+ }
+}
+