Mercurial > code > home > repos > video
annotate video.py @ 49:1bd17c2e5517 default tip
video.py must sign video urls for serve-files.js to serve them
author | drewp@bigasterisk.com |
---|---|
date | Fri, 06 Dec 2024 17:13:51 -0800 |
parents | 882d0bb0f801 |
children |
rev | line source |
---|---|
17
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
1 import asyncio |
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
2 import json |
15 | 3 import logging |
46
882d0bb0f801
clean up; quiet logs; factor out PATH_PREFIX
drewp@bigasterisk.com
parents:
44
diff
changeset
|
4 from functools import partial |
882d0bb0f801
clean up; quiet logs; factor out PATH_PREFIX
drewp@bigasterisk.com
parents:
44
diff
changeset
|
5 from urllib.parse import unquote |
5
75b54be050bc
show one subdir of archive at once, with folders and parents-breadcrumbs
drewp@bigasterisk.com
parents:
4
diff
changeset
|
6 |
46
882d0bb0f801
clean up; quiet logs; factor out PATH_PREFIX
drewp@bigasterisk.com
parents:
44
diff
changeset
|
7 import pymongo.database |
28
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
8 import uvicorn |
3 | 9 from prometheus_client import Gauge |
28
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
10 from sse_starlette.sse import EventSourceResponse |
3 | 11 from starlette.applications import Starlette |
12 from starlette.requests import Request | |
15 | 13 from starlette.responses import HTMLResponse, JSONResponse, Response |
3 | 14 from starlette.routing import Route |
15 from starlette_exporter import PrometheusMiddleware, handle_metrics | |
5
75b54be050bc
show one subdir of archive at once, with folders and parents-breadcrumbs
drewp@bigasterisk.com
parents:
4
diff
changeset
|
16 |
28
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
17 import dl_queue |
49
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
18 from signature_gen import makePlaybackSig |
46
882d0bb0f801
clean up; quiet logs; factor out PATH_PREFIX
drewp@bigasterisk.com
parents:
44
diff
changeset
|
19 import thumbnail |
882d0bb0f801
clean up; quiet logs; factor out PATH_PREFIX
drewp@bigasterisk.com
parents:
44
diff
changeset
|
20 from mongo_required import open_mongo_or_die |
15 | 21 from video_file_store import VideoFileStore |
22 from video_ingest import VideoIngest | |
18 | 23 |
3 | 24 logging.basicConfig(level=logging.DEBUG) |
25 log = logging.getLogger() | |
17
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
26 logging.getLogger('sse_starlette').setLevel(logging.WARNING) |
32 | 27 logging.getLogger('pymongo').setLevel(logging.INFO) |
0 | 28 |
29 | |
3 | 30 def root(req): |
31 return HTMLResponse("api") | |
0 | 32 |
33 | |
6
ccfea3625cf6
render thumbs and display them (no video player at all atm)
drewp@bigasterisk.com
parents:
5
diff
changeset
|
34 async def videos(req: Request) -> JSONResponse: |
44
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
35 # either like /dir1/dir2/ or /dir1/dir2/vid1 |
38
0aea9e55899b
hack in path router - dirs kind of work; putting a video in the path doesn't
drewp@bigasterisk.com
parents:
37
diff
changeset
|
36 subdir = req.query_params.get('subdir', '/') |
49
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
37 user = req.headers.get('x-pomerium-email', '') |
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
38 if not user: |
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
39 raise ValueError('must be logged in') |
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
40 log.info(f'videos for {user!r}') |
44
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
41 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
42 subdir = unquote(subdir) |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
43 webDirRelPath = subdir.rsplit('/', 1)[0] + '/' |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
44 autoplayRelPath = subdir if not subdir.endswith('/') else None |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
45 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
46 log.info(f'loading {webDirRelPath=} {autoplayRelPath=}') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
47 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
48 assert webDirRelPath.startswith('/') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
49 assert webDirRelPath.endswith('/') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
50 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
51 resp: dict[str, object] = { |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
52 'webDirRelPath': webDirRelPath, |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
53 'dirLabel': webDirRelPath.strip('/').split('/')[-1], #todo |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
54 } |
39 | 55 |
44
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
56 d = webDirRelPath[1:-1] or '.' |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
57 log.info(f'loading {d=}') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
58 vfInDir = list(store.findInDir(d)) |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
59 resp["videos"] = [{ |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
60 'webRelPath': '/' + vf.webRelPath, |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
61 'webDataPath': '/' + vf.webDataPath, |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
62 'label': vf.label, |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
63 } for vf in vfInDir] |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
64 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
65 if autoplayRelPath: |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
66 for vf in vfInDir: |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
67 if '/' + vf.webRelPath == autoplayRelPath: |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
68 resp['autoplay'] = { |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
69 'webRelPath': '/' + vf.webRelPath, |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
70 'webDataPath': '/' + vf.webDataPath, |
49
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
71 'label': vf.label, |
1bd17c2e5517
video.py must sign video urls for serve-files.js to serve them
drewp@bigasterisk.com
parents:
46
diff
changeset
|
72 'sig': makePlaybackSig(user, '/' + vf.webDataPath), |
44
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
73 } |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
74 break |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
75 else: |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
76 raise ValueError(f'{autoplayRelPath=} not in dir') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
77 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
78 log.info(f'{subdir=}') |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
79 resp['subdirs'] = [] |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
80 for s in store.findSubdirs(subdir.strip('/') or '/'): |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
81 resp['subdirs'].append({ |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
82 'label': s['label'], |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
83 'path': '/' + s['path'] + '/', |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
84 }) |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
85 |
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
86 return JSONResponse(resp) |
4
c8a41359505c
server provides video listing from disk
drewp@bigasterisk.com
parents:
3
diff
changeset
|
87 |
15 | 88 |
20 | 89 def folderTree(req: Request) -> JSONResponse: |
90 return JSONResponse(store.folderTree()) | |
91 | |
92 | |
15 | 93 async def ingestVideoUrl(req: Request) -> Response: |
23
9e56e86a6814
server supports downloading into a given folder
drewp@bigasterisk.com
parents:
20
diff
changeset
|
94 folder = req.query_params['folder'] |
15 | 95 url = await req.body() |
23
9e56e86a6814
server supports downloading into a given folder
drewp@bigasterisk.com
parents:
20
diff
changeset
|
96 await svc.ingestUrl(url.decode('utf8'), folder) |
15 | 97 return Response(status_code=202) |
98 | |
99 | |
100 async def ingestVideoUpload(req: Request) -> Response: | |
101 name = req.query_params['name'] | |
102 await svc.addContent(name, req.body()) | |
103 return Response(status_code=200) | |
104 | |
105 | |
106 async def ingestQueue(req: Request) -> EventSourceResponse: | |
18 | 107 |
17
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
108 async def g(): |
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
109 async for ev in svc.events(): |
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
110 yield json.dumps(ev) |
18 | 111 |
17
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
112 return EventSourceResponse(g()) |
15 | 113 |
39 | 114 |
37
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
115 def getDiskPath(fs, webRelPath): |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
116 doc = fs.find_one({'webRelPath': webRelPath}) |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
117 if doc is None: |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
118 raise ValueError |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
119 return doc['diskPath'] |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
120 |
39 | 121 |
122 async def getThumbnail(db: pymongo.database.Database, | |
123 req: Request) -> Response: | |
44
239a83d46d48
make the server return urls with the (new) correct slashes
drewp@bigasterisk.com
parents:
39
diff
changeset
|
124 webRelPath = req.query_params['webRelPath'].lstrip('/') |
37
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
125 fs = db.get_collection('fs') |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
126 diskPath = getDiskPath(fs, webRelPath) |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
127 th = db.get_collection('thumb') |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
128 async with asyncio.timeout(10): |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
129 data = await thumbnail.getThumbnailData(th, diskPath) |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
130 return Response(content=data, media_type='image/jpeg') |
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
131 |
15 | 132 |
36
ed16fdbb3996
rewrite WIP. scan fs separately; store in db. thumbs are broken for now
drewp@bigasterisk.com
parents:
32
diff
changeset
|
133 db = open_mongo_or_die().get_database('video') |
ed16fdbb3996
rewrite WIP. scan fs separately; store in db. thumbs are broken for now
drewp@bigasterisk.com
parents:
32
diff
changeset
|
134 store = VideoFileStore(db.get_collection('fs')) |
15 | 135 svc = VideoIngest(store) |
136 | |
18 | 137 |
4
c8a41359505c
server provides video listing from disk
drewp@bigasterisk.com
parents:
3
diff
changeset
|
138 def main(): |
3 | 139 |
140 app = Starlette( | |
141 debug=True, | |
142 routes=[ | |
4
c8a41359505c
server provides video listing from disk
drewp@bigasterisk.com
parents:
3
diff
changeset
|
143 Route('/video/api/', root), |
c8a41359505c
server provides video listing from disk
drewp@bigasterisk.com
parents:
3
diff
changeset
|
144 Route('/video/api/videos', videos), |
20 | 145 Route('/video/api/folderTree', folderTree), |
15 | 146 Route('/video/api/ingest/videoUpload', |
147 ingestVideoUpload, | |
148 methods=['POST']), | |
149 Route('/video/api/ingest/videoUrl', | |
150 ingestVideoUrl, | |
151 methods=['POST']), | |
152 Route('/video/api/ingest/queue', ingestQueue), | |
37
7cacfae58430
thumbnails rewrite - store in db; don't use YT-provided pics for now
drewp@bigasterisk.com
parents:
36
diff
changeset
|
153 Route('/video/api/thumbnail', partial(getThumbnail, db)), |
3 | 154 ], |
155 ) | |
156 | |
157 app.add_middleware(PrometheusMiddleware, app_name='video_api') | |
25 | 158 app.add_route("/video/api/metrics", handle_metrics) |
27 | 159 app.add_route("/metrics", handle_metrics) |
18 | 160 |
17
071943adf000
dnd a file or a url which we'll queue and fetch
drewp@bigasterisk.com
parents:
15
diff
changeset
|
161 app.state.processTask = asyncio.create_task(dl_queue.process()) |
4
c8a41359505c
server provides video listing from disk
drewp@bigasterisk.com
parents:
3
diff
changeset
|
162 return app |
3 | 163 |
39 | 164 |
28
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
165 uvicorn.run(main, |
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
166 host="0.0.0.0", |
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
167 port=8004, |
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
168 log_level=logging.INFO, |
1e058bea3ac2
crash better when mongo is unreachable
drewp@bigasterisk.com
parents:
27
diff
changeset
|
169 factory=True) |