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.
- 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 +158 -300
- litpose_app/ngdist/ng_app/3rdpartylicenses.txt +11 -11
- litpose_app/ngdist/ng_app/index.html +3 -2
- litpose_app/ngdist/ng_app/{main-LJHMLKBL.js → main-VCJFCLFP.js} +221 -148
- litpose_app/ngdist/ng_app/main-VCJFCLFP.js.map +1 -0
- litpose_app/ngdist/ng_app/{styles-4V6RXJMC.css → styles-ZM27COY6.css} +11 -1
- litpose_app/ngdist/ng_app/styles-ZM27COY6.css.map +7 -0
- litpose_app/{run_ffprobe.py → routes/ffprobe.py} +164 -132
- litpose_app/{super_rglob.py → routes/files.py} +108 -48
- 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.1a3.dist-info/METADATA +0 -15
- lightning_pose_app-1.8.1a3.dist-info/RECORD +0 -21
- litpose_app/ngdist/ng_app/main-LJHMLKBL.js.map +0 -1
- litpose_app/ngdist/ng_app/styles-4V6RXJMC.css.map +0 -7
- {lightning_pose_app-1.8.1a3.dist-info → lightning_pose_app-1.8.1a4.dist-info}/WHEEL +0 -0
- /litpose_app/ngdist/ng_app/{app.component-UHVEDPZR.css.map → app.component-UAQUAGNZ.css.map} +0 -0
- /litpose_app/ngdist/ng_app/{project-settings.component-5IRK7U7U.css.map → project-settings.component-HKHIVUJR.css.map} +0 -0
- /litpose_app/ngdist/ng_app/{video-tile.component-XSYKMARQ.css.map → video-tile.component-RDL4BSJ4.css.map} +0 -0
- /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
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,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,,
|