diff video.py @ 6:ccfea3625cf6

render thumbs and display them (no video player at all atm)
author drewp@bigasterisk.com
date Sun, 09 Apr 2023 18:02:25 -0700
parents 75b54be050bc
children 53d99454f394
line wrap: on
line diff
--- a/video.py	Thu Mar 30 20:39:40 2023 -0700
+++ b/video.py	Sun Apr 09 18:02:25 2023 -0700
@@ -1,4 +1,10 @@
+import asyncio
+from dataclasses import dataclass
+from pathlib import Path
+import hashlib
 import logging
+import re
+from typing import Iterable
 
 from prometheus_client import Gauge
 from starlette.applications import Starlette
@@ -7,30 +13,83 @@
 from starlette.routing import Route
 from starlette_exporter import PrometheusMiddleware, handle_metrics
 
-from video_service import findInDir, findSubdirs
+from video_service import VideoFile
 
 logging.basicConfig(level=logging.DEBUG)
 log = logging.getLogger()
 
+def vf(p: Path, label: str):
+    return VideoFile(p, './files/' + str(p.relative_to('/data')), label)
+
+def thumbWebPath(rel: str)->str:
+    return './files/' + rel
+
+@dataclass
+class VideoFileStore:
+    top: Path
+
+    def findInDir(self, subdir: str) -> Iterable[VideoFile]:
+        if subdir[0] != '/': raise ValueError
+        here = self.top / subdir[1:]
+        manifests = list(here.glob('*.mpd'))
+        if manifests:
+            p = manifests[0]
+            label = p.parent.name
+            yield vf(p, label)
+            return
+        for p in sorted(list(here.glob('*.mp4')) + list(here.glob('*.webm'))):
+            label = re.sub(r' \[[^\]]+\]\.\w+', '', p.name)
+            yield vf(p, label)
+
+
+    def findSubdirs(self, subdir: str) -> Iterable:
+        if subdir[0] != '/': raise ValueError
+        here = self.top / subdir[1:]
+        for p in here.iterdir():
+            if p.is_dir() and p.name not in {'_thumb'}:
+                yield {'label': p.name, 'path': '/' + str(p.relative_to(self.top))}
+
+    def thumbPath(self,  vf: VideoFile) -> str:
+        sha256 = hashlib.sha256()       
+        with open(vf.diskPath, 'rb') as f:
+            firstMb = f.read(1<<20)
+            sha256.update(firstMb)
+        cksum = sha256.hexdigest()
+        return f'_thumb/{cksum}.jpg'
+
+    async def getOrCreateThumb(self, vf: VideoFile) -> str:
+        p = self.top / self.thumbPath(vf)
+        if not p.exists():
+            sp = asyncio.create_subprocess_exec('ffmpegthumbnailer',
+            '-s', '250',
+            '-i', str(vf.diskPath),
+            '-o', str(p))
+            await sp
+        return thumbWebPath(str(p.relative_to(self.top)))
+
 
 def root(req):
     return HTMLResponse("api")
 
 
-def videos(req: Request) -> JSONResponse:
+async def videos(req: Request) -> JSONResponse:
     subdir = req.query_params.get('subdir', '/')  # danger user input
+    vfInDir = store.findInDir(subdir)
     return JSONResponse({
         "videos": [{
             'webRelPath': vf.webRelPath,
             'label': vf.label,
-        } for vf in findInDir(subdir)],
+            'thumbRelPath': await store.getOrCreateThumb(vf),
+        } for vf in vfInDir],
         "subdirs":
-        list(findSubdirs(subdir)),
+        list(store.findSubdirs(subdir)),
     })
 
+store = VideoFileStore(top=Path('/data'))
 
 def main():
 
+
     app = Starlette(
         debug=True,
         routes=[