# 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.pathSegs.map((seg) => html`${seg.label}`)}
${this.subdirs.map((s) => html`
${s.label}
`)} - ${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=[