lightning-pose-app 1.8.1a3__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.
Files changed (28) hide show
  1. lightning_pose_app-1.8.1a4.dist-info/METADATA +20 -0
  2. lightning_pose_app-1.8.1a4.dist-info/RECORD +29 -0
  3. litpose_app/config.py +22 -0
  4. litpose_app/deps.py +77 -0
  5. litpose_app/main.py +158 -300
  6. litpose_app/ngdist/ng_app/3rdpartylicenses.txt +11 -11
  7. litpose_app/ngdist/ng_app/index.html +3 -2
  8. litpose_app/ngdist/ng_app/{main-LJHMLKBL.js → main-VCJFCLFP.js} +221 -148
  9. litpose_app/ngdist/ng_app/main-VCJFCLFP.js.map +1 -0
  10. litpose_app/ngdist/ng_app/{styles-4V6RXJMC.css → styles-ZM27COY6.css} +11 -1
  11. litpose_app/ngdist/ng_app/styles-ZM27COY6.css.map +7 -0
  12. litpose_app/{run_ffprobe.py → routes/ffprobe.py} +164 -132
  13. litpose_app/{super_rglob.py → routes/files.py} +108 -48
  14. litpose_app/routes/project.py +72 -0
  15. litpose_app/routes/transcode.py +67 -0
  16. litpose_app/tasks/__init__.py +0 -0
  17. litpose_app/tasks/management.py +2 -0
  18. litpose_app/tasks/transcode_fine.py +7 -0
  19. litpose_app/transcode_fine.py +175 -0
  20. lightning_pose_app-1.8.1a3.dist-info/METADATA +0 -15
  21. lightning_pose_app-1.8.1a3.dist-info/RECORD +0 -21
  22. litpose_app/ngdist/ng_app/main-LJHMLKBL.js.map +0 -1
  23. litpose_app/ngdist/ng_app/styles-4V6RXJMC.css.map +0 -7
  24. {lightning_pose_app-1.8.1a3.dist-info → lightning_pose_app-1.8.1a4.dist-info}/WHEEL +0 -0
  25. /litpose_app/ngdist/ng_app/{app.component-UHVEDPZR.css.map → app.component-UAQUAGNZ.css.map} +0 -0
  26. /litpose_app/ngdist/ng_app/{project-settings.component-5IRK7U7U.css.map → project-settings.component-HKHIVUJR.css.map} +0 -0
  27. /litpose_app/ngdist/ng_app/{video-tile.component-XSYKMARQ.css.map → video-tile.component-RDL4BSJ4.css.map} +0 -0
  28. /litpose_app/ngdist/ng_app/{viewer-page.component-MRTIUFL2.css.map → viewer-page.component-KDHT6XH5.css.map} +0 -0
@@ -1,48 +1,108 @@
1
- import datetime
2
-
3
- from wcmatch import pathlib as w
4
-
5
-
6
- def super_rglob(base_path, pattern=None, no_dirs=False, stat=False):
7
- """
8
- Needs to be performant when searching over large model directory.
9
- Uses wcmatch to exclude directories with extra calls to Path.is_dir.
10
- wcmatch includes features that may be helpful down the line.
11
- """
12
- if pattern is None:
13
- pattern = "**/*"
14
- flags = w.GLOBSTAR
15
- if no_dirs:
16
- flags |= w.NODIR
17
- results = w.Path(base_path).glob(
18
- pattern,
19
- flags=flags,
20
- )
21
- result_dicts = []
22
- for r in results:
23
- stat_info = r.stat() if stat else None
24
- is_dir = False if no_dirs else r.is_dir() if stat else None
25
- if no_dirs and is_dir:
26
- continue
27
- entry_relative_path = r.relative_to(base_path)
28
- d = {
29
- "path": entry_relative_path,
30
- "type": "dir" if is_dir else "file" if is_dir == False else None,
31
- "size": stat_info.st_size if stat_info else None,
32
- # Note: st_birthtime is more reliable for creation time on some systems
33
- "cTime": (
34
- datetime.datetime.fromtimestamp(
35
- getattr(stat_info, "st_birthtime", stat_info.st_ctime)
36
- ).isoformat()
37
- if stat_info
38
- else None
39
- ),
40
- "mTime": (
41
- datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
42
- if stat_info
43
- else None
44
- ),
45
- }
46
-
47
- result_dicts.append(d)
48
- return result_dicts
1
+ from pathlib import Path
2
+
3
+ from fastapi import APIRouter, HTTPException, status
4
+ from pydantic import BaseModel
5
+
6
+ router = APIRouter()
7
+
8
+
9
+ class RGlobRequest(BaseModel):
10
+ baseDir: Path
11
+ pattern: str
12
+ noDirs: bool = False
13
+ stat: bool = False
14
+
15
+
16
+ class RGlobResponseEntry(BaseModel):
17
+ path: Path
18
+
19
+ # Present only if request had stat=True or noDirs=True
20
+ type: str | None
21
+
22
+ # Present only if request had stat=True
23
+
24
+ size: int | None
25
+ # Creation timestamp, ISO format.
26
+ cTime: str | None
27
+ # Modified timestamp, ISO format.
28
+ mTime: str | None
29
+
30
+
31
+ class RGlobResponse(BaseModel):
32
+ entries: list[RGlobResponseEntry]
33
+ relativeTo: Path # this is going to be the same base_dir that was in the request.
34
+
35
+
36
+ @router.post("/app/v0/rpc/rglob")
37
+ def rglob(request: RGlobRequest) -> RGlobResponse:
38
+ # Prevent secrets like /etc/passwd and ~/.ssh/ from being leaked.
39
+ if not (request.pattern.endswith(".csv") or request.pattern.endswith(".mp4")):
40
+ raise HTTPException(
41
+ status_code=status.HTTP_403_FORBIDDEN,
42
+ detail="Only csv and mp4 files are supported.",
43
+ )
44
+
45
+ response = RGlobResponse(entries=[], relativeTo=request.baseDir)
46
+
47
+ results = super_rglob(
48
+ str(request.baseDir),
49
+ pattern=request.pattern,
50
+ no_dirs=request.noDirs,
51
+ stat=request.stat,
52
+ )
53
+ for r in results:
54
+ # Convert dict to pydantic model
55
+ converted = RGlobResponseEntry.model_validate(r)
56
+ response.entries.append(converted)
57
+
58
+ return response
59
+
60
+
61
+ import datetime
62
+
63
+ from wcmatch import pathlib as w
64
+
65
+
66
+ def super_rglob(base_path, pattern=None, no_dirs=False, stat=False):
67
+ """
68
+ Needs to be performant when searching over large model directory.
69
+ Uses wcmatch to exclude directories with extra calls to Path.is_dir.
70
+ wcmatch includes features that may be helpful down the line.
71
+ """
72
+ if pattern is None:
73
+ pattern = "**/*"
74
+ flags = w.GLOBSTAR
75
+ if no_dirs:
76
+ flags |= w.NODIR
77
+ results = w.Path(base_path).glob(
78
+ pattern,
79
+ flags=flags,
80
+ )
81
+ result_dicts = []
82
+ for r in results:
83
+ stat_info = r.stat() if stat else None
84
+ is_dir = False if no_dirs else r.is_dir() if stat else None
85
+ if no_dirs and is_dir:
86
+ continue
87
+ entry_relative_path = r.relative_to(base_path)
88
+ d = {
89
+ "path": entry_relative_path,
90
+ "type": "dir" if is_dir else "file" if is_dir == False else None,
91
+ "size": stat_info.st_size if stat_info else None,
92
+ # Note: st_birthtime is more reliable for creation time on some systems
93
+ "cTime": (
94
+ datetime.datetime.fromtimestamp(
95
+ getattr(stat_info, "st_birthtime", stat_info.st_ctime)
96
+ ).isoformat()
97
+ if stat_info
98
+ else None
99
+ ),
100
+ "mTime": (
101
+ datetime.datetime.fromtimestamp(stat_info.st_mtime).isoformat()
102
+ if stat_info
103
+ else None
104
+ ),
105
+ }
106
+
107
+ result_dicts.append(d)
108
+ return result_dicts
@@ -0,0 +1,72 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ import tomli
5
+ import tomli_w
6
+ from fastapi import APIRouter, Depends
7
+ from pydantic import BaseModel
8
+
9
+ from litpose_app import deps
10
+ from litpose_app.config import Config
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ router = APIRouter()
15
+
16
+
17
+ class ProjectInfo(BaseModel):
18
+ """Class to hold information about the project"""
19
+
20
+ data_dir: Path | None = None
21
+ model_dir: Path | None = None
22
+ views: list[str] | None = None
23
+
24
+
25
+ class GetProjectInfoResponse(BaseModel):
26
+ projectInfo: ProjectInfo | None # None if project info not yet initialized
27
+
28
+
29
+ class SetProjectInfoRequest(BaseModel):
30
+ projectInfo: ProjectInfo
31
+
32
+
33
+ @router.post("/app/v0/rpc/getProjectInfo")
34
+ def get_project_info(
35
+ project_info: ProjectInfo = Depends(deps.project_info),
36
+ ) -> GetProjectInfoResponse:
37
+ return GetProjectInfoResponse(projectInfo=project_info)
38
+
39
+
40
+ @router.post("/app/v0/rpc/setProjectInfo")
41
+ def set_project_info(
42
+ request: SetProjectInfoRequest, config: Config = Depends(deps.config)
43
+ ) -> None:
44
+ try:
45
+ config.PROJECT_INFO_TOML_PATH.parent.mkdir(parents=True, exist_ok=True)
46
+
47
+ project_data_dict = request.projectInfo.model_dump(
48
+ mode="json", exclude_none=True
49
+ )
50
+ try:
51
+ with open(config.PROJECT_INFO_TOML_PATH, "rb") as f:
52
+ existing_project_data = tomli.load(f)
53
+ except FileNotFoundError:
54
+ existing_project_data = {}
55
+
56
+ existing_project_data.update(project_data_dict)
57
+
58
+ with open(config.PROJECT_INFO_TOML_PATH, "wb") as f:
59
+ tomli_w.dump(existing_project_data, f)
60
+
61
+ return None
62
+
63
+ except IOError as e:
64
+ error_message = f"Failed to write project information to file: {str(e)}"
65
+ print(error_message)
66
+ raise e
67
+ except Exception as e:
68
+ error_message = (
69
+ f"An unexpected error occurred while saving project info: {str(e)}"
70
+ )
71
+ print(error_message)
72
+ raise e
@@ -0,0 +1,67 @@
1
+ import asyncio
2
+
3
+ from apscheduler.schedulers.asyncio import AsyncIOScheduler
4
+ from fastapi import APIRouter, Depends
5
+ from pydantic import BaseModel
6
+
7
+ from .files import super_rglob
8
+ from .project import ProjectInfo
9
+ from .. import deps
10
+ from ..tasks import transcode_fine
11
+
12
+ router = APIRouter()
13
+ from litpose_app.config import Config
14
+
15
+
16
+ @router.post("/app/v0/rpc/getFineVideoDir")
17
+ def get_fine_video_dir(config: Config = Depends(deps.config)):
18
+ return {"path": config.FINE_VIDEO_DIR}
19
+
20
+
21
+ class GetFineVideoStatusRequest(BaseModel):
22
+ name: str # just the filename.
23
+
24
+
25
+ @router.post("/app/v0/rpc/getFineVideoStatus")
26
+ async def get_fine_video_status(
27
+ request: GetFineVideoStatusRequest, config: Config = Depends(deps.config)
28
+ ):
29
+ """
30
+ Either returns NotStarted, Done, or InProgress (with SSE ProgressStream)
31
+ """
32
+ return {"path": config.FINE_VIDEO_DIR}
33
+
34
+
35
+ @router.post("/app/v0/rpc/enqueueAllNewFineVideos")
36
+ async def enqueue_all_new_fine_videos(
37
+ config: Config = Depends(deps.config),
38
+ project_info: ProjectInfo = Depends(deps.project_info),
39
+ scheduler: AsyncIOScheduler = Depends(deps.scheduler),
40
+ ):
41
+ # get all mp4 video files that are less than config.AUTO_TRANSCODE_VIDEO_SIZE_LIMIT_MB
42
+ base_path = project_info.data_dir
43
+ result = await asyncio.to_thread(super_rglob, base_path, pattern="*.mp4", stat=True)
44
+
45
+ # Filter videos by size limit
46
+ videos = [
47
+ base_path / entry["path"]
48
+ for entry in result
49
+ if entry["size"]
50
+ and entry["size"] < config.AUTO_TRANSCODE_VIDEO_SIZE_LIMIT_MB * 1000 * 1000
51
+ ]
52
+
53
+ # Create a transcode job per video.
54
+ # The id of the job is just the filename. We assume unique video filenames
55
+ # across the entire dataset.
56
+ for path in videos:
57
+ scheduler.add_job(
58
+ transcode_fine.transcode_video_task,
59
+ id=path.name,
60
+ args=[path, config.FINE_VIDEO_DIR / path.name],
61
+ executor="transcode_pool",
62
+ # executor="debug",
63
+ replace_existing=True,
64
+ misfire_grace_time=None,
65
+ )
66
+
67
+ return "ok"
File without changes
@@ -0,0 +1,2 @@
1
+ # Global registry of active transcoding jobs.
2
+ _transcoding_task_ids: dict[str, str] = {} # Key: videoPath, Value: Task
@@ -0,0 +1,7 @@
1
+ from pathlib import Path
2
+
3
+ from litpose_app.transcode_fine import transcode_file
4
+
5
+
6
+ def transcode_video_task(input_file_path: Path, output_file_path: Path):
7
+ transcode_file(input_file_path, output_file_path)
@@ -0,0 +1,175 @@
1
+ import shutil
2
+ import subprocess
3
+ from multiprocessing import Pool, cpu_count
4
+ from pathlib import Path
5
+
6
+ # --- Configuration ---
7
+ TARGET_SUFFIX = "sec.mp4"
8
+ OUTPUT_SUFFIX_ADDITION = ".fine"
9
+ MAX_CONCURRENCY = 6
10
+ # FFmpeg options for transcoding:
11
+ # -g 1: Intra frame for every frame (Group of Pictures size 1)
12
+ # -c:v libx264: Use libx264 encoder
13
+ # -preset medium: A balance between encoding speed and compression.
14
+ # Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow.
15
+ # -crf 23: Constant Rate Factor. Lower values mean better quality and larger files (0-51, default 23).
16
+ # -c:a copy: Copy audio stream without re-encoding. If audio re-encoding is needed, change this.
17
+ # -y: Overwrite output files without asking.
18
+ FFMPEG_OPTIONS = [
19
+ "-c:v",
20
+ "libx264",
21
+ "-g",
22
+ "1",
23
+ "-preset",
24
+ "medium",
25
+ "-crf",
26
+ "23",
27
+ "-c:a",
28
+ "copy",
29
+ ]
30
+
31
+
32
+ def check_dependencies():
33
+ """Checks if ffmpeg and ffprobe are installed and in PATH."""
34
+ if shutil.which("ffmpeg") is None:
35
+ print("Error: ffmpeg is not installed or not found in PATH.")
36
+ return False
37
+ if shutil.which("ffprobe") is None:
38
+ print("Error: ffprobe is not installed or not found in PATH.")
39
+ return False
40
+ return True
41
+
42
+
43
+ def transcode_file(
44
+ input_file_path: Path,
45
+ output_file_path: Path,
46
+ ) -> tuple[bool, str, Path | None]:
47
+ """
48
+ Transcodes a single video file to have an intra frame for every frame.
49
+ The output file will be named by inserting ".fine" before the final ".mp4"
50
+ and placed in the specified output_dir.
51
+ Example: "video.sec.mp4" -> "video.sec.fine.mp4"
52
+ Returns a tuple: (success_status: bool, message: str, output_path: Path | None)
53
+ """
54
+ try:
55
+
56
+ if output_file_path.exists():
57
+ print(
58
+ f"Output file '{output_file_path.name}' already exists. Skipping transcoding."
59
+ )
60
+ return True, f"Skipped (exists): {output_file_path.name}", output_file_path
61
+
62
+ import sys
63
+
64
+ print(f"Processing: {input_file_path.name} -> {output_file_path.name}")
65
+
66
+ ffmpeg_cmd = [
67
+ "ffmpeg",
68
+ "-i",
69
+ str(input_file_path),
70
+ *FFMPEG_OPTIONS,
71
+ "-y", # Overwrite output without asking (though we check existence above)
72
+ str(output_file_path),
73
+ ]
74
+
75
+ process = subprocess.Popen(
76
+ ffmpeg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
77
+ )
78
+
79
+ stdout, stderr = process.communicate()
80
+
81
+ if process.returncode == 0:
82
+ print(f"Successfully transcoded: {output_file_path.name}")
83
+ return True, f"Success: {output_file_path.name}", output_file_path
84
+ else:
85
+ print(f"Error transcoding '{input_file_path.name}':")
86
+ print(f"FFmpeg stdout:\n{stdout}")
87
+ print(f"FFmpeg stderr:\n{stderr}")
88
+ # Clean up partially created file on error
89
+ if output_file_path.exists():
90
+ try:
91
+ output_file_path.unlink()
92
+ except OSError as e:
93
+ print(
94
+ f"Could not remove partially created file '{output_file_path}': {e}"
95
+ )
96
+ return (
97
+ False,
98
+ f"Error: {input_file_path.name} - FFmpeg failed (code {process.returncode})",
99
+ None,
100
+ )
101
+
102
+ except Exception as e:
103
+ print(f"Error processing '{input_file_path.name}': {e}")
104
+ return False, f"Error: {input_file_path.name} - Exception: {e}", None
105
+
106
+
107
+ def main():
108
+ """
109
+ Main function to find and transcode videos.
110
+ """
111
+ if not check_dependencies():
112
+ return
113
+
114
+ script_dir = Path(__file__).parent # Process files in the script's directory
115
+ # To process files in the current working directory instead:
116
+ # current_dir = Path.cwd()
117
+
118
+ print(
119
+ f"Scanning for '*{TARGET_SUFFIX}' H.264 files in '{script_dir}' and its subdirectories..."
120
+ )
121
+
122
+ # Find all files ending with TARGET_SUFFIX recursively
123
+ files_to_check = list(script_dir.rglob(f"*{TARGET_SUFFIX}"))
124
+
125
+ if not files_to_check:
126
+ print(f"No files found ending with '{TARGET_SUFFIX}'.")
127
+ return
128
+
129
+ print(f"Found {len(files_to_check)} potential files. Checking H.264 codec...")
130
+
131
+ valid_files_to_transcode = []
132
+ for f_path in files_to_check:
133
+ # Ensure it's not an already processed file
134
+ if OUTPUT_SUFFIX_ADDITION + ".mp4" in f_path.name:
135
+ continue
136
+ valid_files_to_transcode.append(f_path)
137
+
138
+ if not valid_files_to_transcode:
139
+ print("No H.264 files matching the criteria need transcoding.")
140
+ return
141
+
142
+ print(f"\nFound {len(valid_files_to_transcode)} H.264 files to transcode:")
143
+ for f in valid_files_to_transcode:
144
+ print(f" - {f.name}")
145
+
146
+ # Determine number of processes
147
+ num_processes = min(MAX_CONCURRENCY, cpu_count(), len(valid_files_to_transcode))
148
+ print(f"\nStarting transcoding with up to {num_processes} parallel processes...\n")
149
+
150
+ output_file_paths = []
151
+ for f in valid_files_to_transcode:
152
+ base_name = f.name[: -len(TARGET_SUFFIX)]
153
+ output_file_name = f"{base_name}{TARGET_SUFFIX.replace('.mp4', '')}{OUTPUT_SUFFIX_ADDITION}.mp4"
154
+ output_file_path = f.parent / output_file_name
155
+ output_file_paths.append(output_file_path)
156
+ # In this main function, output_dir is still the parent of the input file
157
+ # For RPC, we will specify FINE_VIDEO_DIR as output_dir
158
+ with Pool(processes=num_processes) as pool:
159
+ # A dummy output_dir for the script's main function; not used by RPC.
160
+ # It ensures compatibility if this script were run standalone.
161
+ # In the RPC, we'll pass FINE_VIDEO_DIR explicitly.
162
+ results = pool.starmap(
163
+ transcode_file,
164
+ [(f, of) for f, of in zip(valid_files_to_transcode, output_file_paths)],
165
+ )
166
+
167
+ print("\n--- Transcoding Summary ---")
168
+ for result in results:
169
+ print(result)
170
+ print("--------------------------")
171
+ print("All tasks completed.")
172
+
173
+
174
+ if __name__ == "__main__":
175
+ main()
@@ -1,15 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: lightning-pose-app
3
- Version: 1.8.1a3
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
- Requires-Dist: fastapi
12
- Requires-Dist: tomli
13
- Requires-Dist: tomli_w
14
- Requires-Dist: uvicorn[standard]
15
- Requires-Dist: wcmatch
@@ -1,21 +0,0 @@
1
- litpose_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- litpose_app/main.py,sha256=ZjD5i_zUVbSxsHPEn6J2TEtPTCEAdtqP5_-DndakixY,10037
3
- litpose_app/ngdist/ng_app/3rdpartylicenses.txt,sha256=k34Q8jZikvFWsgFXPlQxFJVcqMXDEiKKzC_VIEK8jNs,25366
4
- litpose_app/ngdist/ng_app/app.component-UHVEDPZR.css.map,sha256=e6lXWzmK3xppKK3tXHUccK1yGZqd1kzyTpDH0F1nC2g,344
5
- litpose_app/ngdist/ng_app/error-dialog.component-HYLQSJEP.css.map,sha256=zJuF-LfB994Y1IrnIz38mariDFb8yucffbWPXgHGbvw,355
6
- litpose_app/ngdist/ng_app/favicon.ico,sha256=QtbXVfx3HI-WqWh_kkBIQyYzJsDmLw_3Y4_UN7pBepE,15406
7
- litpose_app/ngdist/ng_app/index.html,sha256=ATacgBvjlF3gLRzFCtDnS3shRzTOp8Rzu4rKfltBDOQ,22954
8
- litpose_app/ngdist/ng_app/main-LJHMLKBL.js,sha256=nXrPvnVQwhHQJdHMsF-41nu5Mu7K6WARxWSei2g0y58,2722143
9
- litpose_app/ngdist/ng_app/main-LJHMLKBL.js.map,sha256=ltcuu1XsSXJv0p_KjbUSPBKi9zyAy4sVQXmJxZWNkos,5557811
10
- litpose_app/ngdist/ng_app/prerendered-routes.json,sha256=p53cyKEVGQ6cGUee02kUdBp9HbdPChFTUp78gHJVBf4,18
11
- litpose_app/ngdist/ng_app/project-settings.component-5IRK7U7U.css.map,sha256=v5tyba9p8ec3ZbHYyyUGTEFdEAsqT0l7JqtGRGjki6w,371
12
- litpose_app/ngdist/ng_app/styles-4V6RXJMC.css,sha256=mZ4r-BPkV1KTv3FvvjCJWuUygILiOzl8Jzjesyo0P8U,69300
13
- litpose_app/ngdist/ng_app/styles-4V6RXJMC.css.map,sha256=Fm7m4Uakz0HLL1DMEyfCAAXoFia46_3Eab27wowU9tU,74911
14
- litpose_app/ngdist/ng_app/video-player-controls.component-C4JZHYJ2.css.map,sha256=vX-dgeDCUCPLiea4Qy9O_EBm6IzzwB7R_uSBa0qU5Go,771
15
- litpose_app/ngdist/ng_app/video-tile.component-XSYKMARQ.css.map,sha256=_pZ7FxqOAu535JfrRv1TSKgRpDyQvn9Q0U39KHyJ980,332
16
- litpose_app/ngdist/ng_app/viewer-page.component-MRTIUFL2.css.map,sha256=Uf1FgoCiF_qJpD4Ggk34Dq7kM73Q7i7NT6h3m30tbaY,211
17
- litpose_app/run_ffprobe.py,sha256=7acBcSJ-3t8N_6HZ2KrTrMkivPsxKlGuYzI6yNhaXEs,4975
18
- litpose_app/super_rglob.py,sha256=kgSIra2xznEHhi9kBUzO0SpBb6PkUJCct0WSS33bRmM,1619
19
- lightning_pose_app-1.8.1a3.dist-info/METADATA,sha256=2JCRkaIImYE4r6Y-UcBvGGe7tkRfXlGH2X2PlrMih7A,473
20
- lightning_pose_app-1.8.1a3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
21
- lightning_pose_app-1.8.1a3.dist-info/RECORD,,