# HG changeset patch
# User drewp@bigasterisk.com
# Date 1681088545 25200
# Node ID ccfea3625cf695526feca91e307f3c679441b752
# Parent 75b54be050bcf3d0d1235e4054ee35dbc64b5893
render thumbs and display them (no video player at all atm)
diff -r 75b54be050bc -r ccfea3625cf6 Dockerfile
--- a/Dockerfile Thu Mar 30 20:39:40 2023 -0700
+++ b/Dockerfile Sun Apr 09 18:02:25 2023 -0700
@@ -17,7 +17,7 @@
RUN pnpm set registry "https://bigasterisk.com/js/"
WORKDIR /opt
-RUN apt update && apt-get install -y git
+RUN apt update && apt-get install -y git ffmpegthumbnailer
COPY pyproject.toml pdm.lock ./
RUN pdm sync
diff -r 75b54be050bc -r ccfea3625cf6 src/VideoPage.ts
--- a/src/VideoPage.ts Thu Mar 30 20:39:40 2023 -0700
+++ b/src/VideoPage.ts Sun Apr 09 18:02:25 2023 -0700
@@ -5,6 +5,7 @@
interface VideoFile {
webRelPath: string;
label: string;
+ thumbRelPath: string;
}
interface Subdir {
label: string;
@@ -53,6 +54,8 @@
a {
color: white;
font-size: 20px;
+ text-transform: uppercase;
+ text-underline-offset: 10px;
}
#path-segs > span {
color: white;
@@ -86,7 +89,7 @@
${this.subdirs.map((s) => html``)}
- ${this.videos.map((v) => html``)}
+ ${this.videos.map((v) => html``)}
`;
}
}
diff -r 75b54be050bc -r ccfea3625cf6 src/VideoSection.ts
--- a/src/VideoSection.ts Thu Mar 30 20:39:40 2023 -0700
+++ b/src/VideoSection.ts Sun Apr 09 18:02:25 2023 -0700
@@ -5,6 +5,7 @@
@customElement("video-section")
export class VideoSection extends LitElement {
@property({ type: String }) manifest: string | undefined;
+ @property({ type: String }) thumbRelPath: string | undefined;
@property({ type: String }) title: string = "(unknown)";
@property({ type: String }) big: boolean = false;
@@ -37,11 +38,11 @@
const tx = (document.body.clientWidth - inw * scl) / 2,
ty = (document.body.clientHeight - inh * scl) / 2;
const style = this.big ? `transform: translate(${-inx - inw / 2}px,${-iny - inh / 2}px) scale(${outh / inh}) translate(${tx}px,${ty}px);` : "";
- console.log(document.body.clientWidth);
return html`
${this.title}
-
+
+
`;
}
diff -r 75b54be050bc -r ccfea3625cf6 video.py
--- 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=[