From: Christian Heller <c.heller@plomlompom.de>
Date: Wed, 25 Dec 2024 18:28:56 +0000 (+0100)
Subject: To file data view, add duration as per ffprobe.
X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/static/%7B%7B%20web_path%20%7D%7D/calendar?a=commitdiff_plain;h=b68e80d357225cdd846fe0a9871f4e33631b2e4d;p=ytplom

To file data view, add duration as per ffprobe.
---

diff --git a/src/requirements.txt b/src/requirements.txt
index a0a02a1..cf78ba3 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -3,3 +3,4 @@ Jinja2==3.1.4
 python-mpv==1.0.7
 scp==0.15.0
 yt-dlp==2024.12.06
+ffmpeg-python==0.2.0
diff --git a/src/templates/file_data.tmpl b/src/templates/file_data.tmpl
index da8726f..d4dcf8b 100644
--- a/src/templates/file_data.tmpl
+++ b/src/templates/file_data.tmpl
@@ -30,6 +30,11 @@ td.tag_checkboxes { width: 1em; }
 <td><a href="/{{page_names.yt_result}}/{{file.yt_id}}">{{file.yt_id}}</a>
 </tr>
 
+<tr>
+<th>duration</th>
+<td>{{file.ffprobed_duration}}</td>
+</tr>
+
 <tr>
 <th>tags</th>
 <td>
diff --git a/src/ytplom/misc.py b/src/ytplom/misc.py
index 596ad7c..a68edbe 100644
--- a/src/ytplom/misc.py
+++ b/src/ytplom/misc.py
@@ -14,6 +14,7 @@ from threading import Thread
 from queue import Queue
 from sqlite3 import Cursor
 # non-included libs
+from ffmpeg import probe as ffprobe  # type: ignore
 import googleapiclient.discovery  # type: ignore
 from mpv import MPV  # type: ignore
 from yt_dlp import YoutubeDL  # type: ignore
@@ -84,6 +85,15 @@ def ensure_expected_dirs(expected_dirs: list[Path]) -> None:
         dir_path.mkdir(parents=True, exist_ok=True)
 
 
+def _readable_seconds(seconds: int) -> str:
+    """Represent seconds in (up-to-hours) hexagesimal division."""
+    seconds_str = str(seconds % 60)
+    minutes_str = str(seconds // 60)
+    hours_str = str(seconds // (60 * 60))
+    return ':'.join([f'0{s}' if len(s) == 1 else s
+                     for s in (hours_str, minutes_str, seconds_str)])
+
+
 class TagSet:
     """Collection of tags as used in VideoFile.tags."""
 
@@ -292,11 +302,7 @@ class YoutubeVideo(DbData):
             if dur_char in time_dur:
                 dur_str, time_dur = time_dur.split(dur_char)
                 seconds += int(dur_str) * len_seconds
-        seconds_str = str(seconds % 60)
-        minutes_str = str(seconds // 60)
-        hours_str = str(seconds // (60 * 60))
-        self.duration = ':'.join([f'0{s}' if len(s) == 1 else s for s
-                                  in (hours_str, minutes_str, seconds_str)])
+        self.duration = _readable_seconds(seconds)
 
     @classmethod
     def get_all_for_query(cls,
@@ -438,6 +444,16 @@ class VideoFile(DbData):
             return -1
         return self.full_path.stat().st_size / (1024 * 1024)
 
+    @property
+    def ffprobed_duration(self) -> str:
+        """Return human-friendly formatting of file duration as per ffprobe."""
+        json = ffprobe(self.full_path)
+        duration_str = json['format']['duration']
+        m_seconds_str = duration_str.split('.')[1]
+        duration_float = float(duration_str)
+        seconds = int(duration_float)
+        return f'{_readable_seconds(seconds)}.{m_seconds_str}'
+
     @property
     def present(self) -> bool:
         """Return if file exists in filesystem."""