Mercurial > code > home > repos > video
changeset 37:7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
author | drewp@bigasterisk.com |
---|---|
date | Tue, 03 Dec 2024 19:28:11 -0800 |
parents | ed16fdbb3996 |
children | 0aea9e55899b |
files | ingest.py src/VideoPage.ts thumbnail.py video.py video_file_store.py |
diffstat | 5 files changed, 72 insertions(+), 28 deletions(-) [+] |
line wrap: on
line diff
--- a/ingest.py Tue Dec 03 00:08:22 2024 -0800 +++ b/ingest.py Tue Dec 03 19:28:11 2024 -0800 @@ -16,6 +16,7 @@ durationSec """ +import asyncio import logging from pathlib import Path import re @@ -25,6 +26,7 @@ import pymongo.database import pymongo.collection from mongo_required import open_mongo_or_die +import thumbnail logging.basicConfig(level=logging.INFO) log = logging.getLogger() @@ -89,18 +91,26 @@ for fn in files: p = root / fn if p.suffix not in VIDEO_EXTNS: - if p.suffix == '.webp': + if p.suffix in ['.jpg','.webp']: # youtube thumbnail is ok in here continue log.info(f'ignoring {p=} {p.suffix=}') continue _updateOneFile(p, fs, source) - -# thumb = db.get_collection('thumb') -# probe = db.get_collection('probe') +async def updateThumbnails(db: pymongo.database.Database): + fs = db.get_collection('fs') + thumb = db.get_collection('thumb') + n=0 + for doc in fs.find({'type': 'file'}): + n+=1 + # if n>10: + # log.info('updateThumbnails: stop') + # break + await thumbnail.createThumbnail(thumb, doc['diskPath']) if __name__ == '__main__': while True: updateFs(db, sources) + asyncio.run(updateThumbnails(db)) time.sleep(600)
--- a/src/VideoPage.ts Tue Dec 03 00:08:22 2024 -0800 +++ b/src/VideoPage.ts Tue Dec 03 19:28:11 2024 -0800 @@ -91,6 +91,9 @@ `, ]; render() { + const thumbSrc = (v: VideoFile) => { + return '/video/api/thumbnail?webRelPath='+encodeURIComponent(v.webRelPath); + }; return html` <sl-breadcrumb> ${this.pathSegs.map( @@ -105,7 +108,7 @@ <div class="listing"> ${this.subdirs.map((s) => html`<div class="subdir"><a href="${"./?" + subdirQuery(s.path)}">${s.label}</a></div>`)} ${this.videos.map( - (v) => html`<video-section @playVideo=${this.playVideo} thumbRelPath="${v.thumbRelPath}" title="${v.label}" manifest="/video/files/${v.webDataPath}"></video-section>` + (v) => html`<video-section @playVideo=${this.playVideo} thumbRelPath=${thumbSrc(v)} title="${v.label}" manifest="/video/files/${v.webDataPath}"></video-section>` )} </div> <p><a href="ingest/">Add new videos...</a></p>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/thumbnail.py Tue Dec 03 19:28:11 2024 -0800 @@ -0,0 +1,32 @@ +import asyncio +from tempfile import NamedTemporaryFile +import pymongo.collection +import logging +log = logging.getLogger('thumb') + +async def getThumbnailData(coll: pymongo.collection.Collection, diskPath: str) -> bytes: + doc=coll.find_one({'diskPath':diskPath}) + if doc is None: + raise Exception(f"no thumb found for {diskPath}") + return doc['thumbData'] + +async def createThumbnail(coll: pymongo.collection.Collection, diskPath: str): + if coll.find_one({'diskPath':diskPath}): + return + coll.delete_one({'diskPath':diskPath}) + + # diskPath could be a YT sidecar file? + + thumbFile = NamedTemporaryFile(suffix='.jpg') + log.info(f'createThumbnail: {diskPath=} to {thumbFile.name=}') + proc = await asyncio.create_subprocess_exec('ffmpegthumbnailer', + '-s', '250', '-i', + diskPath, '-o', thumbFile.name) + await proc.wait() + if proc.returncode != 0: + log.error(f'createThumbnail: {proc.returncode=}') + return + thumbFile.seek(0) + imgData = thumbFile.read() + log.info(f'createThumbnail: {len(imgData)=}') + coll.insert_one({'diskPath':diskPath,'thumbData':imgData})
--- a/video.py Tue Dec 03 00:08:22 2024 -0800 +++ b/video.py Tue Dec 03 19:28:11 2024 -0800 @@ -1,7 +1,7 @@ import asyncio +from functools import partial import json import logging -from pathlib import Path import uvicorn from prometheus_client import Gauge @@ -16,6 +16,8 @@ from video_file_store import VideoFileStore from video_ingest import VideoIngest from mongo_required import open_mongo_or_die +import pymongo.database +import thumbnail logging.basicConfig(level=logging.DEBUG) log = logging.getLogger() @@ -66,6 +68,22 @@ 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'] + 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')) @@ -87,6 +105,7 @@ ingestVideoUrl, methods=['POST']), Route('/video/api/ingest/queue', ingestQueue), + Route('/video/api/thumbnail', partial(getThumbnail, db)), ], ) @@ -97,7 +116,6 @@ app.state.processTask = asyncio.create_task(dl_queue.process()) return app - uvicorn.run(main, host="0.0.0.0", port=8004,
--- a/video_file_store.py Tue Dec 03 00:08:22 2024 -0800 +++ b/video_file_store.py Tue Dec 03 19:28:11 2024 -0800 @@ -29,7 +29,7 @@ for doc in self.fs.find({ 'type': 'file', 'webRelParent': webRelParent - }): + }, sort=[('label', 1)]): yield VideoFile(Path(doc['diskPath']), doc['webRelPath'], doc['webDataPath'], doc['label']) @@ -39,31 +39,12 @@ 'dir', 'webRelParent': '.' if subdir == '/' else subdir - }): + }, sort=[('label', 1)]): yield { 'label': doc['label'], 'path': doc['webRelPath'], } - def thumbPath(self, vf: VideoFile) -> str: - return '_thumb/' + vf.webRelPath - 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: - raise - 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))) - async def save(self, name: str, chunks: Iterator[bytes]): raise p = self.top / name