Skip to content

Commit 8aac9a7

Browse files
cleong110AmitMY
andauthored
Feature/recursive videos to poses (#126)
* CDL: minor doc typo fix * videos_to_poses: add recursive and video_suffix options * CDL take out accidentally-added comment in docs * Check all vid extensions, account for name collisions, updated glob to check .pose files for speed * Take out the THIS IS A PROBLEM from v0.1.md * Fix #126 (comment) * directory.py: make pose_files a set. * Fix ugly if statement * Remove unneeded else * count instead of unneeded list, also refactoring SUPPORTED_VIDEO_FORMATS properly * CDL a bit more reformatting * Revert "Take out the THIS IS A PROBLEM from v0.1.md" This reverts commit 87346c0. * Update v0.1.md --------- Co-authored-by: Colin Leong <--unset> Co-authored-by: Amit Moryossef <[email protected]>
1 parent 4af357c commit 8aac9a7

File tree

3 files changed

+143
-28
lines changed

3 files changed

+143
-28
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Alternatively, use a different testing framework to run tests, such as pytest. T
222222
* Or employ pytest:
223223

224224
```bash
225+
# From src/python directory
225226
pytest .
226227
# or for a single file
227228
pytest pose_format/tensorflow/masked/tensor_test.py

docs/specs/v0.1.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
\[`unsigned short` Green]
2525
\[`unsigned short` Blue]
2626

27+
2728
# Body
2829
\[`unsined short` FPS]
2930
\[`unsined short` Number of frames] # THIS IS A PROBLEM
+141-28
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,158 @@
11
import argparse
2-
import os
3-
2+
from pathlib import Path
43
from pose_format.bin.pose_estimation import pose_video, parse_additional_config
4+
from typing import List
5+
import logging
56
from tqdm import tqdm
67

8+
# Note: untested other than .mp4. Support for .webm may have issues: https://github.com/sign-language-processing/pose/pull/126
9+
SUPPORTED_VIDEO_FORMATS = [".mp4", ".mov", ".avi", ".mkv", ".flv", ".wmv", ".webm"]
10+
11+
12+
def find_videos_with_missing_pose_files(
13+
directory: Path,
14+
video_suffixes: List[str] = None,
15+
recursive: bool = False,
16+
keep_video_suffixes: bool = False,
17+
) -> List[Path]:
18+
"""
19+
Finds videos with missing .pose files.
20+
21+
Parameters
22+
----------
23+
directory: Path,
24+
Directory to search for videos in.
25+
video_suffixes: List[str], optional
26+
Suffixes to look for, e.g. [".mp4", ".webm"]. If None, will use _SUPPORTED_VIDEO_FORMATS
27+
recursive: bool, optional
28+
Whether to look for video files recursively, or just the top-level. Defaults to false.
29+
keep_video_suffixes: bool, optional
30+
If true, when checking will append .pose suffix (e.g. foo.mp4->foo.mp4.pose, foo.webm->foo.webm.pose),
31+
If false, will replace it (foo.mp4 becomes foo.pose, and foo.webm ALSO becomes foo.pose).
32+
Default is false, which can cause name collisions.
33+
34+
Returns
35+
-------
36+
List[Path]
37+
List of video paths without corresponding .pose files.
38+
"""
39+
40+
# Prevents the common gotcha with mutable default arg lists:
41+
# https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments
42+
if video_suffixes is None:
43+
video_suffixes = SUPPORTED_VIDEO_FORMATS
44+
45+
glob_method = getattr(directory, "rglob" if recursive else "glob")
46+
all_files = list(glob_method(f"*"))
47+
video_files = [path for path in all_files if path.suffix in video_suffixes]
48+
pose_files = {path for path in all_files if path.suffix == ".pose"}
749

8-
def removesuffix(text: str, suffix: str):
9-
if text.endswith(suffix):
10-
return text[:-len(suffix)]
11-
else:
12-
return text
50+
videos_with_missing_pose_files = []
1351

52+
for vid_path in video_files:
53+
corresponding_pose = get_corresponding_pose_path(video_path=vid_path, keep_video_suffixes=keep_video_suffixes)
54+
if corresponding_pose not in pose_files:
55+
videos_with_missing_pose_files.append(vid_path)
1456

15-
def find_missing_pose_files(directory: str):
16-
all_files = os.listdir(directory)
17-
mp4_files = [f for f in all_files if f.endswith(".mp4")]
18-
pose_files = {removesuffix(f, ".pose") for f in all_files if f.endswith(".pose")}
19-
missing_pose_files = []
57+
return videos_with_missing_pose_files
2058

21-
for mp4_file in mp4_files:
22-
base_name = removesuffix(mp4_file, ".mp4")
23-
if base_name not in pose_files:
24-
missing_pose_files.append(os.path.join(directory, mp4_file))
2559

26-
return sorted(missing_pose_files)
60+
def get_corresponding_pose_path(video_path: Path, keep_video_suffixes: bool = False) -> Path:
61+
"""
62+
Given a video path, and whether to keep the suffix, returns the expected corresponding path with .pose extension.
63+
64+
Parameters
65+
----------
66+
video_path : Path
67+
Path to a video file
68+
keep_video_suffixes : bool, optional
69+
Whether to keep suffix (e.g. foo.mp4 -> foo.mp4.pose)
70+
or replace (foo.mp4->foo.pose). Defaults to replace.
71+
72+
Returns
73+
-------
74+
Path
75+
pathlib Path
76+
"""
77+
if keep_video_suffixes:
78+
return video_path.with_name(f"{video_path.name}.pose")
79+
return video_path.with_suffix(".pose")
2780

2881

2982
def main():
3083
parser = argparse.ArgumentParser()
31-
parser.add_argument('--format',
32-
choices=['mediapipe'],
33-
default='mediapipe',
34-
type=str,
35-
help='type of pose estimation to use')
36-
parser.add_argument("--directory", type=str, required=True)
37-
parser.add_argument('--additional-config', type=str, help='additional configuration for the pose estimator')
84+
parser.add_argument(
85+
"-f",
86+
"--format",
87+
choices=["mediapipe"],
88+
default="mediapipe",
89+
type=str,
90+
help="type of pose estimation to use",
91+
)
92+
parser.add_argument(
93+
"-d",
94+
"--directory",
95+
type=Path,
96+
required=True,
97+
help="Directory to search for videos in",
98+
)
99+
parser.add_argument(
100+
"-r",
101+
"--recursive",
102+
action="store_true",
103+
help="Whether to search for videos recursively",
104+
)
105+
parser.add_argument(
106+
"--keep-video-suffixes",
107+
action="store_true",
108+
help="Whether to drop the video extension (output for foo.mp4 becomes foo.pose, and foo.webm ALSO becomes foo.pose) or append to it (foo.mp4 becomes foo.mp4.pose, foo.webm output is foo.webm.pose). If there are multiple videos with the same basename but different extensions, this will create a .pose file for each. Otherwise only the first video will be posed.",
109+
)
110+
parser.add_argument(
111+
"--video-suffixes",
112+
type=str,
113+
choices=SUPPORTED_VIDEO_FORMATS,
114+
default=SUPPORTED_VIDEO_FORMATS,
115+
help="Video extensions to search for. Defaults to searching for all supported.",
116+
)
117+
parser.add_argument(
118+
"--additional-config",
119+
type=str,
120+
help="additional configuration for the pose estimator",
121+
)
38122
args = parser.parse_args()
39123

40-
missing_pose_files = find_missing_pose_files(args.directory)
124+
videos_with_missing_pose_files = find_videos_with_missing_pose_files(
125+
args.directory,
126+
video_suffixes=args.video_suffixes,
127+
recursive=args.recursive,
128+
keep_video_suffixes=args.keep_video_suffixes,
129+
)
130+
131+
print(f"Found {len(videos_with_missing_pose_files)} videos missing pose files.")
132+
133+
pose_files_that_will_be_created = {get_corresponding_pose_path(vid_path, args.keep_video_suffixes) for vid_path in videos_with_missing_pose_files}
134+
135+
if len(pose_files_that_will_be_created) < len(videos_with_missing_pose_files):
136+
continue_input = input(
137+
f"With current naming strategy (without --keep-video-suffixes), name collisions will result in only {len(pose_files_that_will_be_created)} .pose files being created. Continue? [y/n]"
138+
)
139+
if continue_input.lower() != "y":
140+
print(f"Exiting. To keep video suffixes and avoid collisions, use --keep-video-suffixes")
141+
exit()
142+
41143
additional_config = parse_additional_config(args.additional_config)
42144

43-
for mp4_path in tqdm(missing_pose_files):
44-
pose_file_name = removesuffix(mp4_path, ".mp4") + ".pose"
45-
pose_video(mp4_path, pose_file_name, args.format, additional_config)
145+
pose_with_no_errors_count = 0
146+
147+
for vid_path in tqdm(videos_with_missing_pose_files):
148+
try:
149+
pose_path = get_corresponding_pose_path(video_path=vid_path, keep_video_suffixes=args.keep_video_suffixes)
150+
if pose_path.is_file():
151+
print(f"Skipping {vid_path}, corresponding .pose file already created.")
152+
continue
153+
pose_video(vid_path, pose_path, args.format, additional_config)
154+
pose_with_no_errors_count += 1
155+
except ValueError as e:
156+
print(f"ValueError on {vid_path}")
157+
logging.exception(e)
158+
print(f"Successfully created pose files for {pose_with_no_errors_count}/{len(videos_with_missing_pose_files)} video files")

0 commit comments

Comments
 (0)