view video.py @ 12:e60be5d74c07

css edits and ingest link
author drewp@bigasterisk.com
date Sat, 15 Apr 2023 15:20:12 -0700
parents ccfea3625cf6
children 53d99454f394
line wrap: on
line source

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
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse
from starlette.routing import Route
from starlette_exporter import PrometheusMiddleware, handle_metrics

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")


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,
            'thumbRelPath': await store.getOrCreateThumb(vf),
        } for vf in vfInDir],
        "subdirs":
        list(store.findSubdirs(subdir)),
    })

store = VideoFileStore(top=Path('/data'))

def main():


    app = Starlette(
        debug=True,
        routes=[
            Route('/video/api/', root),
            Route('/video/api/videos', videos),
        ],
    )

    app.add_middleware(PrometheusMiddleware, app_name='video_api')
    app.add_route("/metrics", handle_metrics)
    return app


app = main()