lightning-pose-app 1.8.1a2__py3-none-any.whl → 1.8.1a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightning_pose_app-1.8.1a4.dist-info/METADATA +20 -0
- lightning_pose_app-1.8.1a4.dist-info/RECORD +29 -0
- litpose_app/config.py +22 -0
- litpose_app/deps.py +77 -0
- litpose_app/main.py +46 -188
- litpose_app/ngdist/ng_app/3rdpartylicenses.txt +11 -11
- litpose_app/ngdist/ng_app/index.html +3 -2
- litpose_app/ngdist/ng_app/{main-WFYIUX2C.js → main-VCJFCLFP.js} +184 -61
- litpose_app/ngdist/ng_app/main-VCJFCLFP.js.map +1 -0
- litpose_app/ngdist/ng_app/{styles-AJ6NQDUD.css → styles-ZM27COY6.css} +37 -7
- litpose_app/ngdist/ng_app/styles-ZM27COY6.css.map +7 -0
- litpose_app/ngdist/ng_app/video-tile.component-RDL4BSJ4.css.map +7 -0
- litpose_app/{run_ffprobe.py → routes/ffprobe.py} +34 -2
- litpose_app/{super_rglob.py → routes/files.py} +60 -0
- litpose_app/routes/project.py +72 -0
- litpose_app/routes/transcode.py +67 -0
- litpose_app/tasks/__init__.py +0 -0
- litpose_app/tasks/management.py +2 -0
- litpose_app/tasks/transcode_fine.py +7 -0
- litpose_app/transcode_fine.py +175 -0
- lightning_pose_app-1.8.1a2.dist-info/METADATA +0 -15
- lightning_pose_app-1.8.1a2.dist-info/RECORD +0 -20
- litpose_app/ngdist/ng_app/main-WFYIUX2C.js.map +0 -1
- litpose_app/ngdist/ng_app/styles-AJ6NQDUD.css.map +0 -7
- {lightning_pose_app-1.8.1a2.dist-info → lightning_pose_app-1.8.1a4.dist-info}/WHEEL +0 -0
- /litpose_app/ngdist/ng_app/{app.component-IZ5OUDH2.css.map → app.component-UAQUAGNZ.css.map} +0 -0
- /litpose_app/ngdist/ng_app/{project-settings.component-BXKZMYM3.css.map → project-settings.component-HKHIVUJR.css.map} +0 -0
- /litpose_app/ngdist/ng_app/{viewer-page.component-KIYG73MW.css.map → viewer-page.component-KDHT6XH5.css.map} +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: lightning-pose-app
|
|
3
|
+
Version: 1.8.1a4
|
|
4
|
+
Summary:
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: fastapi (>=0.115.0,<0.116.0)
|
|
13
|
+
Requires-Dist: honcho (>=2.0,<3.0)
|
|
14
|
+
Requires-Dist: httpx (>=0.28.0,<0.29.0) ; extra == "dev"
|
|
15
|
+
Requires-Dist: pytest (>=8.4.0,<8.5.0) ; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-mock (>=3.14.0,<3.15.0) ; extra == "dev"
|
|
17
|
+
Requires-Dist: tomli (>=2.2.0,<2.3.0)
|
|
18
|
+
Requires-Dist: tomli_w (>=1.2.0,<1.3.0)
|
|
19
|
+
Requires-Dist: uvicorn[standard] (>=0.34.0,<0.35.0)
|
|
20
|
+
Requires-Dist: wcmatch (>=10.0,<11.0)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
litpose_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
litpose_app/config.py,sha256=6ZqU71kILT6q1IdYgvYKthYQa2cY9nMwgHeRih24F6s,861
|
|
3
|
+
litpose_app/deps.py,sha256=P0o762ufSW-9nqlpYPRtlrxpKBJMzPZ1LLfTDfTl-4U,2552
|
|
4
|
+
litpose_app/main.py,sha256=ftSNNRnToLN_DpRb_EfgarrxWhDmMsMX2fku4qNrzAU,5397
|
|
5
|
+
litpose_app/ngdist/ng_app/3rdpartylicenses.txt,sha256=8IgJBztx-L-9cLvY7pJB5PSXQJiBowGNUNV7knEAeq0,25355
|
|
6
|
+
litpose_app/ngdist/ng_app/app.component-UAQUAGNZ.css.map,sha256=e6lXWzmK3xppKK3tXHUccK1yGZqd1kzyTpDH0F1nC2g,344
|
|
7
|
+
litpose_app/ngdist/ng_app/error-dialog.component-HYLQSJEP.css.map,sha256=zJuF-LfB994Y1IrnIz38mariDFb8yucffbWPXgHGbvw,355
|
|
8
|
+
litpose_app/ngdist/ng_app/favicon.ico,sha256=QtbXVfx3HI-WqWh_kkBIQyYzJsDmLw_3Y4_UN7pBepE,15406
|
|
9
|
+
litpose_app/ngdist/ng_app/index.html,sha256=y1By3uv-f39NnrfG7hmIPhzC0f0FEDY0U70oqmU6NlQ,23004
|
|
10
|
+
litpose_app/ngdist/ng_app/main-VCJFCLFP.js,sha256=qKG_Ev1-OoVeTeqIVv9DKN8YmfIp2zn6-gBUNFVW__4,2725077
|
|
11
|
+
litpose_app/ngdist/ng_app/main-VCJFCLFP.js.map,sha256=f68Eh0pGhxR0Ol3rkdj4ifPljiw-rnv4JgFBWXsdDmA,5561146
|
|
12
|
+
litpose_app/ngdist/ng_app/prerendered-routes.json,sha256=p53cyKEVGQ6cGUee02kUdBp9HbdPChFTUp78gHJVBf4,18
|
|
13
|
+
litpose_app/ngdist/ng_app/project-settings.component-HKHIVUJR.css.map,sha256=v5tyba9p8ec3ZbHYyyUGTEFdEAsqT0l7JqtGRGjki6w,371
|
|
14
|
+
litpose_app/ngdist/ng_app/styles-ZM27COY6.css,sha256=3bpOGHzSsXAbzY4LgMt2d5xCLL0uY2HjdURJj1Rr3r0,69517
|
|
15
|
+
litpose_app/ngdist/ng_app/styles-ZM27COY6.css.map,sha256=w1T6js2TzjwM4XKfCmyGpfWeqkqzolJbll3wXCRl8Go,75027
|
|
16
|
+
litpose_app/ngdist/ng_app/video-player-controls.component-C4JZHYJ2.css.map,sha256=vX-dgeDCUCPLiea4Qy9O_EBm6IzzwB7R_uSBa0qU5Go,771
|
|
17
|
+
litpose_app/ngdist/ng_app/video-tile.component-RDL4BSJ4.css.map,sha256=_pZ7FxqOAu535JfrRv1TSKgRpDyQvn9Q0U39KHyJ980,332
|
|
18
|
+
litpose_app/ngdist/ng_app/viewer-page.component-KDHT6XH5.css.map,sha256=Uf1FgoCiF_qJpD4Ggk34Dq7kM73Q7i7NT6h3m30tbaY,211
|
|
19
|
+
litpose_app/routes/ffprobe.py,sha256=UVKDu4ghXkYNuzI-91KPMNpQukHV9Goeps7ex_lG_SU,5526
|
|
20
|
+
litpose_app/routes/files.py,sha256=HHyug226s_M4jMi8q7oI2dTWHTKu_J_c7vIbG0rdk9s,3102
|
|
21
|
+
litpose_app/routes/project.py,sha256=pQW7zFdGHMy_eE0Z1bIXs9QLkV1-_MwFHsZR51-H6uQ,1957
|
|
22
|
+
litpose_app/routes/transcode.py,sha256=9C4mMNgh0W7BHI2gdEnnoxMHVAoXag3eCXYhqOvUte4,2071
|
|
23
|
+
litpose_app/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
litpose_app/tasks/management.py,sha256=9wKT8i3CE8ueRCP3DpNd21UMy2_3Kn_LMLBp0dGStFc,120
|
|
25
|
+
litpose_app/tasks/transcode_fine.py,sha256=bX8OblwMO_xaXXqZXJmmQPprCYIpPGHiboyEJli6FHs,209
|
|
26
|
+
litpose_app/transcode_fine.py,sha256=zJKj1ozTSnmAEOddiqdy3ekRKdEAiNm2oMe9bcx520c,6142
|
|
27
|
+
lightning_pose_app-1.8.1a4.dist-info/METADATA,sha256=sIyFToit6PIpDi4jTpjALkGwPXPp-u0l6znOtkpfsIQ,793
|
|
28
|
+
lightning_pose_app-1.8.1a4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
29
|
+
lightning_pose_app-1.8.1a4.dist-info/RECORD,,
|
litpose_app/config.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Routes should not access this directly, if they want to be able to
|
|
2
|
+
modify these in unit tests.
|
|
3
|
+
Instead, prefer to inject `config: deps.config into the route using FastAPI's dependency injection.
|
|
4
|
+
See https://fastapi.tiangolo.com/tutorial/dependencies/."""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Consider `pydantic_settings.BaseSettings` for potential future needs.
|
|
12
|
+
class Config(BaseModel):
|
|
13
|
+
PROJECT_INFO_TOML_PATH: Path = Path("~/.lightning_pose/project.toml").expanduser()
|
|
14
|
+
|
|
15
|
+
## Video transcoding settings
|
|
16
|
+
|
|
17
|
+
# Directory where finely transcoded videos are stored
|
|
18
|
+
FINE_VIDEO_DIR: Path = Path("~/.lightning_pose/finevideos").expanduser()
|
|
19
|
+
|
|
20
|
+
# We'll automatically transcode videos with size under this limit.
|
|
21
|
+
# Larger ones will have to be manually triggered (design TBD).
|
|
22
|
+
AUTO_TRANSCODE_VIDEO_SIZE_LIMIT_MB: int = 30
|
litpose_app/deps.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dependencies that can be injected into routes.
|
|
3
|
+
This has the benefit of making tests easier to write, as you can override dependencies.
|
|
4
|
+
See FastAPI Dependency Injection docs: https://fastapi.tiangolo.com/tutorial/dependencies/
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import math
|
|
11
|
+
import os
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from apscheduler.executors.debug import DebugExecutor
|
|
15
|
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
16
|
+
from apscheduler.executors.pool import ThreadPoolExecutor
|
|
17
|
+
from fastapi import Depends
|
|
18
|
+
|
|
19
|
+
from litpose_app.config import Config
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def config() -> Config:
|
|
25
|
+
"""Dependency that provides the app config object."""
|
|
26
|
+
from .main import app
|
|
27
|
+
|
|
28
|
+
if not hasattr(app.state, "config"):
|
|
29
|
+
app.state.config = Config()
|
|
30
|
+
return app.state.config
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def scheduler() -> AsyncIOScheduler:
|
|
34
|
+
"""Dependency that provides the app's APScheduler instance."""
|
|
35
|
+
from .main import app
|
|
36
|
+
|
|
37
|
+
if not hasattr(app.state, "scheduler"):
|
|
38
|
+
# ffmpeg parallelizes transcoding to the optimal degree, but
|
|
39
|
+
# that doesn't always saturate a machine with a lot of cores.
|
|
40
|
+
# i.e. on a 24 logical core machine (12 physical * 2 hyperthreads per core)
|
|
41
|
+
# 3 was the ideal number of max_workers. Let's just guesstimate that
|
|
42
|
+
# ffmpeg uses 10 cores? No scientific evidence, but ceil(24/10) => 3.
|
|
43
|
+
transcode_workers = math.ceil(os.cpu_count() / 10)
|
|
44
|
+
executors = {
|
|
45
|
+
"transcode_pool": ThreadPoolExecutor(max_workers=transcode_workers),
|
|
46
|
+
"debug": DebugExecutor(),
|
|
47
|
+
}
|
|
48
|
+
app.state.scheduler = AsyncIOScheduler(executors=executors)
|
|
49
|
+
return app.state.scheduler
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if TYPE_CHECKING:
|
|
53
|
+
from .routes.project import ProjectInfo
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def project_info(config: Config = Depends(config)) -> ProjectInfo:
|
|
57
|
+
import tomli
|
|
58
|
+
from .routes.project import ProjectInfo
|
|
59
|
+
|
|
60
|
+
from pydantic import ValidationError
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Open the file in binary read mode, as recommended by tomli
|
|
64
|
+
with open(config.PROJECT_INFO_TOML_PATH, "rb") as f:
|
|
65
|
+
# Load the TOML data into a Python dictionary
|
|
66
|
+
toml_data = tomli.load(f)
|
|
67
|
+
|
|
68
|
+
# Unpack the dictionary into the Pydantic model
|
|
69
|
+
return ProjectInfo(**toml_data)
|
|
70
|
+
except FileNotFoundError:
|
|
71
|
+
return None
|
|
72
|
+
except tomli.TOMLDecodeError as e:
|
|
73
|
+
logger.error(f"Could not decode pyproject.toml. Invalid syntax: {e}")
|
|
74
|
+
raise
|
|
75
|
+
except ValidationError as e:
|
|
76
|
+
logger.error(f"pyproject.toml is invalid. {e}")
|
|
77
|
+
raise
|
litpose_app/main.py
CHANGED
|
@@ -1,24 +1,53 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
1
4
|
from pathlib import Path
|
|
2
5
|
from textwrap import dedent
|
|
3
6
|
|
|
4
|
-
import tomli
|
|
5
|
-
import tomli_w
|
|
6
|
-
from fastapi import FastAPI, HTTPException
|
|
7
|
-
from fastapi.responses import FileResponse
|
|
8
|
-
import sys
|
|
9
7
|
import uvicorn
|
|
10
|
-
from
|
|
8
|
+
from fastapi import FastAPI, HTTPException, APIRouter, Request
|
|
9
|
+
from fastapi.responses import FileResponse
|
|
11
10
|
from starlette import status
|
|
12
|
-
from starlette.requests import Request
|
|
13
11
|
from starlette.responses import Response
|
|
14
12
|
from starlette.staticfiles import StaticFiles
|
|
15
13
|
|
|
16
|
-
from .
|
|
17
|
-
|
|
14
|
+
from . import deps
|
|
15
|
+
|
|
16
|
+
## Setup logging
|
|
17
|
+
logging.basicConfig(
|
|
18
|
+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
19
|
+
)
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## Configure additional things to happen on server startup and shutdown.
|
|
24
|
+
@asynccontextmanager
|
|
25
|
+
async def lifespan(app: FastAPI):
|
|
26
|
+
# Start apscheduler, which is responsible for executing background tasks
|
|
27
|
+
logger.info("Application startup: Initializing scheduler...")
|
|
28
|
+
scheduler = deps.scheduler()
|
|
29
|
+
scheduler.start()
|
|
30
|
+
|
|
31
|
+
yield # Application is now ready to receive requests
|
|
32
|
+
|
|
33
|
+
logger.info("Application shutdown: Shutting down scheduler...")
|
|
34
|
+
if scheduler and scheduler.running:
|
|
35
|
+
scheduler.shutdown()
|
|
36
|
+
logger.info("Scheduler shut down.")
|
|
37
|
+
else:
|
|
38
|
+
logger.warning("Scheduler not found or not running during shutdown.")
|
|
18
39
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
|
|
41
|
+
app = FastAPI(lifespan=lifespan)
|
|
42
|
+
|
|
43
|
+
router = APIRouter()
|
|
44
|
+
from .routes import ffprobe, files, project, transcode
|
|
45
|
+
|
|
46
|
+
router.include_router(ffprobe.router)
|
|
47
|
+
router.include_router(files.router)
|
|
48
|
+
router.include_router(project.router)
|
|
49
|
+
router.include_router(transcode.router)
|
|
50
|
+
app.include_router(router)
|
|
22
51
|
|
|
23
52
|
|
|
24
53
|
@app.exception_handler(Exception)
|
|
@@ -41,17 +70,6 @@ async def debug_exception_handler(request: Request, exc: Exception):
|
|
|
41
70
|
)
|
|
42
71
|
|
|
43
72
|
|
|
44
|
-
PROJECT_INFO_TOML_PATH = Path("~/.lightning_pose/project.toml").expanduser()
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class ProjectInfo(BaseModel):
|
|
48
|
-
"""Class to hold information about the project"""
|
|
49
|
-
|
|
50
|
-
data_dir: Path | None = None
|
|
51
|
-
model_dir: Path | None = None
|
|
52
|
-
views: list[str] | None = None
|
|
53
|
-
|
|
54
|
-
|
|
55
73
|
"""
|
|
56
74
|
All our methods are RPC style (http url corresponds to method name).
|
|
57
75
|
They should be POST requests, /rpc/<method_name>.
|
|
@@ -64,160 +82,9 @@ error in a dialog to the user. So if the client is supposed to
|
|
|
64
82
|
handle the error in any way, for example, special form validation UX
|
|
65
83
|
like underlining the invalid field,
|
|
66
84
|
then the information about the error should be included in a valid
|
|
67
|
-
response object rather than raised as a python error.
|
|
85
|
+
response object rather than raised as a python error.
|
|
68
86
|
"""
|
|
69
87
|
|
|
70
|
-
|
|
71
|
-
class GetProjectInfoResponse(BaseModel):
|
|
72
|
-
projectInfo: ProjectInfo | None # None if project info not yet initialized
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@app.post("/app/v0/rpc/getProjectInfo")
|
|
76
|
-
def get_project_info() -> GetProjectInfoResponse:
|
|
77
|
-
try:
|
|
78
|
-
# Open the file in binary read mode, as recommended by tomli
|
|
79
|
-
with open(PROJECT_INFO_TOML_PATH, "rb") as f:
|
|
80
|
-
# Load the TOML data into a Python dictionary
|
|
81
|
-
toml_data = tomli.load(f)
|
|
82
|
-
|
|
83
|
-
# Unpack the dictionary into the Pydantic model
|
|
84
|
-
# Pydantic will handle all the validation from here.
|
|
85
|
-
obj = ProjectInfo(**toml_data)
|
|
86
|
-
return GetProjectInfoResponse(projectInfo=obj)
|
|
87
|
-
|
|
88
|
-
except FileNotFoundError:
|
|
89
|
-
return GetProjectInfoResponse(projectInfo=None)
|
|
90
|
-
except tomli.TOMLDecodeError as e:
|
|
91
|
-
print(f"Error: Could not decode the TOML file. Invalid syntax: {e}")
|
|
92
|
-
raise
|
|
93
|
-
except ValidationError as e:
|
|
94
|
-
# Pydantic's validation error is very informative
|
|
95
|
-
print(f"Error: Configuration is invalid. {e}")
|
|
96
|
-
raise
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class SetProjectInfoRequest(BaseModel):
|
|
100
|
-
projectInfo: ProjectInfo
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@app.post("/app/v0/rpc/setProjectInfo")
|
|
104
|
-
def set_project_info(request: SetProjectInfoRequest) -> None:
|
|
105
|
-
try:
|
|
106
|
-
PROJECT_INFO_TOML_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
-
|
|
108
|
-
# Convert the Pydantic model to a dictionary for TOML serialization.
|
|
109
|
-
# Use mode=json to make the resulting dict json-serializable (and thus
|
|
110
|
-
# also toml serializable)
|
|
111
|
-
project_data_dict = request.projectInfo.model_dump(
|
|
112
|
-
mode="json", exclude_none=True
|
|
113
|
-
)
|
|
114
|
-
try:
|
|
115
|
-
with open(PROJECT_INFO_TOML_PATH, "rb") as f:
|
|
116
|
-
existing_project_data = tomli.load(f)
|
|
117
|
-
except FileNotFoundError:
|
|
118
|
-
existing_project_data = {}
|
|
119
|
-
|
|
120
|
-
# Apply changes onto existing data, i.e. PATCH semantics.
|
|
121
|
-
existing_project_data.update(project_data_dict)
|
|
122
|
-
|
|
123
|
-
# Open the file in binary write mode to write the TOML data
|
|
124
|
-
with open(PROJECT_INFO_TOML_PATH, "wb") as f:
|
|
125
|
-
tomli_w.dump(existing_project_data, f)
|
|
126
|
-
|
|
127
|
-
return None
|
|
128
|
-
|
|
129
|
-
except IOError as e:
|
|
130
|
-
# This catches errors related to file operations (e.g., permissions, disk full)
|
|
131
|
-
error_message = f"Failed to write project information to file: {str(e)}"
|
|
132
|
-
print(error_message) # Log server-side
|
|
133
|
-
raise e
|
|
134
|
-
except Exception as e: # Catch any other unexpected errors
|
|
135
|
-
error_message = (
|
|
136
|
-
f"An unexpected error occurred while saving project info: {str(e)}"
|
|
137
|
-
)
|
|
138
|
-
print(error_message) # Log server-side
|
|
139
|
-
raise e
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class RGlobRequest(BaseModel):
|
|
143
|
-
baseDir: Path
|
|
144
|
-
pattern: str
|
|
145
|
-
noDirs: bool = False
|
|
146
|
-
stat: bool = False
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
class RGlobResponseEntry(BaseModel):
|
|
150
|
-
path: Path
|
|
151
|
-
|
|
152
|
-
# Present only if request had stat=True or noDirs=True
|
|
153
|
-
type: str | None
|
|
154
|
-
|
|
155
|
-
# Present only if request had stat=True
|
|
156
|
-
|
|
157
|
-
size: int | None
|
|
158
|
-
# Creation timestamp, ISO format.
|
|
159
|
-
cTime: str | None
|
|
160
|
-
# Modified timestamp, ISO format.
|
|
161
|
-
mTime: str | None
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class RGlobResponse(BaseModel):
|
|
165
|
-
entries: list[RGlobResponseEntry]
|
|
166
|
-
relativeTo: Path # this is going to be the same base_dir that was in the request.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
@app.post("/app/v0/rpc/rglob")
|
|
170
|
-
def rglob(request: RGlobRequest) -> RGlobResponse:
|
|
171
|
-
# Prevent secrets like /etc/passwd and ~/.ssh/ from being leaked.
|
|
172
|
-
if not (request.pattern.endswith(".csv") or request.pattern.endswith(".mp4")):
|
|
173
|
-
raise HTTPException(
|
|
174
|
-
status_code=status.HTTP_403_FORBIDDEN,
|
|
175
|
-
detail="Only csv and mp4 files are supported.",
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
response = RGlobResponse(entries=[], relativeTo=request.baseDir)
|
|
179
|
-
|
|
180
|
-
results = super_rglob(
|
|
181
|
-
str(request.baseDir),
|
|
182
|
-
pattern=request.pattern,
|
|
183
|
-
no_dirs=request.noDirs,
|
|
184
|
-
stat=request.stat,
|
|
185
|
-
)
|
|
186
|
-
for r in results:
|
|
187
|
-
# Convert dict to pydantic model
|
|
188
|
-
converted = RGlobResponseEntry.model_validate(r)
|
|
189
|
-
response.entries.append(converted)
|
|
190
|
-
|
|
191
|
-
return response
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class FFProbeRequest(BaseModel):
|
|
195
|
-
path: Path
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
class FFProbeResponse(BaseModel):
|
|
199
|
-
codec: str
|
|
200
|
-
width: int
|
|
201
|
-
height: int
|
|
202
|
-
fps: int
|
|
203
|
-
duration: float
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
@app.post("/app/v0/rpc/ffprobe")
|
|
207
|
-
def ffprobe(request: FFProbeRequest) -> FFProbeResponse:
|
|
208
|
-
if request.path.suffix != ".mp4":
|
|
209
|
-
raise HTTPException(
|
|
210
|
-
status_code=status.HTTP_403_FORBIDDEN,
|
|
211
|
-
detail="Only mp4 files are supported.",
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
result = run_ffprobe(str(request.path))
|
|
215
|
-
|
|
216
|
-
response = FFProbeResponse.model_validate(result)
|
|
217
|
-
|
|
218
|
-
return response
|
|
219
|
-
|
|
220
|
-
|
|
221
88
|
"""
|
|
222
89
|
File server to serve csv and video files.
|
|
223
90
|
FileResponse supports range requests for video buffering.
|
|
@@ -251,11 +118,12 @@ def read_file(file_path: Path):
|
|
|
251
118
|
# This is necessary to use HTTP2 for faster concurrent request performance (ng serve doesn't support it).
|
|
252
119
|
###########################################################################
|
|
253
120
|
|
|
121
|
+
|
|
254
122
|
# Serve ng assets (js, css)
|
|
255
123
|
STATIC_DIR = Path(__file__).parent / "ngdist" / "ng_app"
|
|
256
124
|
if not STATIC_DIR.is_dir():
|
|
257
125
|
message = dedent(
|
|
258
|
-
|
|
126
|
+
"""
|
|
259
127
|
⚠️ Warning: We couldn't find the necessary static assets (like HTML, CSS, JavaScript files).
|
|
260
128
|
As a result, only the HTTP API is currently running.
|
|
261
129
|
|
|
@@ -276,25 +144,15 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR, check_dir=False), name="s
|
|
|
276
144
|
|
|
277
145
|
|
|
278
146
|
@app.get("/favicon.ico")
|
|
279
|
-
def favicon():
|
|
147
|
+
async def favicon():
|
|
280
148
|
return FileResponse(Path(__file__).parent / "ngdist" / "ng_app" / "favicon.ico")
|
|
281
149
|
|
|
282
150
|
|
|
283
151
|
# Catch-all route. serve index.html.
|
|
284
152
|
@app.get("/{full_path:path}")
|
|
285
|
-
def index(
|
|
153
|
+
async def index():
|
|
286
154
|
return FileResponse(Path(__file__).parent / "ngdist" / "ng_app" / "index.html")
|
|
287
155
|
|
|
288
156
|
|
|
289
|
-
def get_static_files_if_needed():
|
|
290
|
-
cache_dir = Path("~/.lightning_pose/cache").expanduser()
|
|
291
|
-
# Version check
|
|
292
|
-
# App should run with "latest compatible version"
|
|
293
|
-
# this means that if lightning pose is installed, it gets the latest version compatible with that version.
|
|
294
|
-
# otherwise it gets just the latest version.
|
|
295
|
-
# Download the files?
|
|
296
|
-
|
|
297
|
-
|
|
298
157
|
def run_app(host: str, port: int):
|
|
299
|
-
get_static_files_if_needed()
|
|
300
158
|
uvicorn.run(app, host=host, port=port)
|
|
@@ -341,17 +341,17 @@ License: "Apache-2.0"
|
|
|
341
341
|
Package: tslib
|
|
342
342
|
License: "0BSD"
|
|
343
343
|
|
|
344
|
-
Copyright (c) Microsoft Corporation.
|
|
345
|
-
|
|
346
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
347
|
-
purpose with or without fee is hereby granted.
|
|
348
|
-
|
|
349
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
350
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
351
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
352
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
353
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
354
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
344
|
+
Copyright (c) Microsoft Corporation.
|
|
345
|
+
|
|
346
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
347
|
+
purpose with or without fee is hereby granted.
|
|
348
|
+
|
|
349
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
350
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
351
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
352
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
353
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
354
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
355
355
|
PERFORMANCE OF THIS SOFTWARE.
|
|
356
356
|
--------------------------------------------------------------------------------
|
|
357
357
|
Package: @angular/common
|
|
@@ -329,6 +329,7 @@
|
|
|
329
329
|
--color-green-400: oklch(79.2% 0.209 151.711);
|
|
330
330
|
--color-sky-100: oklch(95.1% 0.026 236.824);
|
|
331
331
|
--color-sky-700: oklch(50% 0.134 242.749);
|
|
332
|
+
--color-gray-400: oklch(70.7% 0.022 261.325);
|
|
332
333
|
--color-black: #000;
|
|
333
334
|
--spacing: 0.25rem;
|
|
334
335
|
--container-xs: 20rem;
|
|
@@ -444,8 +445,8 @@
|
|
|
444
445
|
}
|
|
445
446
|
}
|
|
446
447
|
}
|
|
447
|
-
</style><link rel="stylesheet" href="/static/styles-
|
|
448
|
+
</style><link rel="stylesheet" href="/static/styles-ZM27COY6.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="/static/styles-ZM27COY6.css"></noscript></head>
|
|
448
449
|
<body>
|
|
449
450
|
<app-root></app-root>
|
|
450
|
-
<script src="/static/main-
|
|
451
|
+
<script src="/static/main-VCJFCLFP.js" type="module"></script></body>
|
|
451
452
|
</html>
|