view video.py @ 44:239a83d46d48

make the server return urls with the (new) correct slashes
author drewp@bigasterisk.com
date Fri, 06 Dec 2024 01:01:05 -0800
parents b5b29f6ef5cb
children 882d0bb0f801
line wrap: on
line source

import asyncio
from functools import partial
import json
import logging

import uvicorn
from prometheus_client import Gauge
from sse_starlette.sse import EventSourceResponse
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import HTMLResponse, JSONResponse, Response
from starlette.routing import Route
from starlette_exporter import PrometheusMiddleware, handle_metrics

import dl_queue
from video_file_store import VideoFileStore
from video_ingest import VideoIngest
from mongo_required import open_mongo_or_die
import pymongo.database
import thumbnail
from urllib.parse import unquote

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger()
logging.getLogger('sse_starlette').setLevel(logging.WARNING)
logging.getLogger('pymongo').setLevel(logging.INFO)


def root(req):
    return HTMLResponse("api")


async def videos(req: Request) -> JSONResponse:
    # either like /dir1/dir2/ or /dir1/dir2/vid1
    subdir = req.query_params.get('subdir', '/')

    subdir = unquote(subdir)
    webDirRelPath = subdir.rsplit('/', 1)[0] + '/'
    autoplayRelPath = subdir if not subdir.endswith('/') else None

    log.info(f'loading {webDirRelPath=} {autoplayRelPath=}')

    assert webDirRelPath.startswith('/')
    assert webDirRelPath.endswith('/')

    resp: dict[str, object] = {
        'webDirRelPath': webDirRelPath,
        'dirLabel': webDirRelPath.strip('/').split('/')[-1],  #todo
    }

    d = webDirRelPath[1:-1] or '.'
    log.info(f'loading {d=}')
    vfInDir = list(store.findInDir(d))
    resp["videos"] = [{
        'webRelPath': '/' + vf.webRelPath,
        'webDataPath': '/' + vf.webDataPath,
        'label': vf.label,
    } for vf in vfInDir]

    if autoplayRelPath:
        for vf in vfInDir:
            if '/' + vf.webRelPath == autoplayRelPath:
                resp['autoplay'] = {
                    'webRelPath': '/' + vf.webRelPath,
                    'webDataPath': '/' + vf.webDataPath,
                    'label': vf.label
                }
                break
        else:
            raise ValueError(f'{autoplayRelPath=} not in dir')

    log.info(f'{subdir=}')
    resp['subdirs'] = []
    for s in store.findSubdirs(subdir.strip('/') or '/'):
        resp['subdirs'].append({
            'label': s['label'],
            'path': '/' + s['path'] + '/',
        })

    return JSONResponse(resp)


def folderTree(req: Request) -> JSONResponse:
    return JSONResponse(store.folderTree())


async def ingestVideoUrl(req: Request) -> Response:
    folder = req.query_params['folder']
    url = await req.body()
    await svc.ingestUrl(url.decode('utf8'), folder)
    return Response(status_code=202)


async def ingestVideoUpload(req: Request) -> Response:
    name = req.query_params['name']
    await svc.addContent(name, req.body())
    return Response(status_code=200)


async def ingestQueue(req: Request) -> EventSourceResponse:

    async def g():
        async for ev in svc.events():
            yield json.dumps(ev)

    return EventSourceResponse(g())


def getDiskPath(fs, webRelPath):
    doc = fs.find_one({'webRelPath': webRelPath})
    if doc is None:
        raise ValueError
    return doc['diskPath']


async def getThumbnail(db: pymongo.database.Database,
                       req: Request) -> Response:
    webRelPath = req.query_params['webRelPath'].lstrip('/')
    fs = db.get_collection('fs')
    diskPath = getDiskPath(fs, webRelPath)
    th = db.get_collection('thumb')
    async with asyncio.timeout(10):
        data = await thumbnail.getThumbnailData(th, diskPath)
        return Response(content=data, media_type='image/jpeg')


db = open_mongo_or_die().get_database('video')
store = VideoFileStore(db.get_collection('fs'))
svc = VideoIngest(store)


def main():

    app = Starlette(
        debug=True,
        routes=[
            Route('/video/api/', root),
            Route('/video/api/videos', videos),
            Route('/video/api/folderTree', folderTree),
            Route('/video/api/ingest/videoUpload',
                  ingestVideoUpload,
                  methods=['POST']),
            Route('/video/api/ingest/videoUrl',
                  ingestVideoUrl,
                  methods=['POST']),
            Route('/video/api/ingest/queue', ingestQueue),
            Route('/video/api/thumbnail', partial(getThumbnail, db)),
        ],
    )

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

    app.state.processTask = asyncio.create_task(dl_queue.process())
    return app


uvicorn.run(main,
            host="0.0.0.0",
            port=8004,
            log_level=logging.INFO,
            factory=True)