Skip to content

[Feature Request]: Automatic timestamping #677

Open
@dot-mike

Description

@dot-mike

I've read the documentation

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' });
+  }
+}
+

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions