Visitar URL original
update `list_videos_in_folder` by deruyter92 · Pull Request #3303 · DeepLabCut/DeepLabCut · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2d41bee
Update `list_video_files_in_folder` only filter explicitly provided f…
deruyter92 May 4, 2026
96e31be
add test for list_videos_in_folder
deruyter92 May 4, 2026
3c63e90
Add deprecation utils: decorator for deprecated functions
deruyter92 May 7, 2026
b92103e
move `list_videos_in_folder` to `collect_video_paths` in auxfun_video…
deruyter92 May 7, 2026
ee4d9b1
export `deeplabcut.collect_video_paths(..)` from auxfun_videos.py
deruyter92 May 8, 2026
2014a59
update `collect_video_paths`: add exclude patterns
deruyter92 May 8, 2026
f6f1ee5
deprecate auxfun `get_list_of_videos` in favor of `collect_video_paths`
deruyter92 May 8, 2026
f6b33d2
add pytest deprecation marker `test_get_list_of_videos`
deruyter92 May 8, 2026
659bf7d
Mark `get_video_list` as deprecated (keep for now for backward compat…
deruyter92 May 8, 2026
983951d
fix test_collect_video_paths: rename keyword -> extensions
deruyter92 May 8, 2026
7d71306
Adjust `extensions` parameter: flexibly coerce str / list / tuple ->…
deruyter92 May 8, 2026
bce0ba9
Merge branch 'main' into jaap/update_list_videos_in_folder
C-Achard May 12, 2026
adadf08
`collect_video_paths`: sort videos if shuffle=False
deruyter92 May 12, 2026
ce8bcf5
update docstring non-recursive directory scanning.
deruyter92 May 12, 2026
75a4d0a
update collect_video_paths: DEFAULT_EXCLUDE_PATTERNS
deruyter92 May 12, 2026
481d3fa
update _coerce_extensions: only "" and None normalize to None
deruyter92 May 12, 2026
8ca9b49
fix instance check in _coerce_video_extensions
deruyter92 May 12, 2026
39557ec
fix pytest: assert alphabetic ordering if shuffle=False
deruyter92 May 12, 2026
39cfd94
fix empty string case in _coerce_extensions
deruyter92 May 12, 2026
77a05dc
Add structured deprecation info and warnings (#3326)
C-Achard May 12, 2026
5610a49
Merge branch 'main' into jaap/update_list_videos_in_folder
C-Achard May 13, 2026
792d35d
guarantee unchanged behavior for deprecated `get_list_of_videos` and …
deruyter92 May 13, 2026
fe38152
add warning if still unsupported extensions encountered
deruyter92 May 13, 2026
69981f1
Consequently adjust `videotype` signatures of collect_video_path callers
deruyter92 May 15, 2026
096973e
refactor collect_video_paths: add warnings, raise for empty sequence.
deruyter92 May 15, 2026
0929e7b
update tests for collect_video_paths
deruyter92 May 15, 2026
996b220
Merge branch 'main' into jaap/update_list_videos_in_folder
MMathisLab May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
move list_videos_in_folder to collect_video_paths in auxfun_video…
…s.py
  • Loading branch information
deruyter92 committed May 7, 2026
commit b92103ef66dab2e3607620db2da72df8b3b94f7a
3 changes: 2 additions & 1 deletion deeplabcut/pose_estimation_pytorch/apis/tracking_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from deeplabcut.pose_estimation_pytorch.apis.videos import VideoIterator
from deeplabcut.pose_estimation_pytorch.task import Task
from deeplabcut.pose_tracking_pytorch import create_triplets_dataset
from deeplabcut.utils.auxfun_videos import collect_video_paths


def build_feature_extraction_runner(
Expand Down Expand Up @@ -240,7 +241,7 @@ def create_tracking_dataset(
modelprefix=modelprefix,
)

videos = utils.list_videos_in_folder(videos, videotype)
videos = collect_video_paths(videos, videotype)
for video_path in videos:
print(f"Loading {video_path}")
video = VideoIterator(video_path, cropping=cropping)
Expand Down
4 changes: 2 additions & 2 deletions deeplabcut/pose_estimation_pytorch/apis/tracklets.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
from deeplabcut.core.inferenceutils import Assembly
from deeplabcut.pose_estimation_pytorch.apis.utils import (
get_scorer_name,
list_videos_in_folder,
parse_snapshot_index_for_analysis,
)
from deeplabcut.pose_estimation_pytorch.data.dlcloader import DLCLoader
from deeplabcut.utils.auxfun_videos import collect_video_paths


def convert_detections2tracklets(
Expand Down Expand Up @@ -124,7 +124,7 @@ def convert_detections2tracklets(
modelprefix=modelprefix,
)

videos = list_videos_in_folder(videos, videotype)
videos = collect_video_paths(videos, videotype)
if len(videos) == 0:
print(f"No videos were found in {videos}")
return
Expand Down
60 changes: 9 additions & 51 deletions deeplabcut/pose_estimation_pytorch/apis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from __future__ import annotations

import logging
import random
from collections.abc import Callable
from pathlib import Path

Expand Down Expand Up @@ -65,7 +64,9 @@
)
from deeplabcut.pose_estimation_pytorch.task import Task
from deeplabcut.pose_estimation_pytorch.utils import resolve_device
from deeplabcut.utils import auxfun_videos, auxiliaryfunctions
from deeplabcut.utils import auxiliaryfunctions
from deeplabcut.utils.auxfun_videos import collect_video_paths
from deeplabcut.utils.deprecation import deprecated


def parse_snapshot_index_for_analysis(
Expand Down Expand Up @@ -293,60 +294,17 @@ def get_scorer_name(
return f"DLC_{name}_{task}{date}shuffle{shuffle}_{snapshot_uid}"


@deprecated("deeplabcut.utils.auxfun_videos.collect_video_paths", since="3.0.0")
def list_videos_in_folder(
data_path: str | Path | list[str | Path],
video_type: str | None = None,
shuffle: bool = False,
) -> list[Path]:
"""
Args:
data_path: Path or list of paths to folders containing videos, or individual
video files. Can be a mix of directories and files.
video_type: The type of video to filter for (e.g., "mp4", ".mp4").
- If set: filter both directory contents and supplied files.
- If ``None``, explicitly-supplied files are used as-is. (Directory
contents are filtered using ``SUPPORTED_VIDEOS`` by default).
shuffle: Whether to shuffle the order of videos. If False, videos are returned
in sorted order for deterministic behavior.

Returns:
The paths of videos to analyze. Duplicate paths are removed.

Raises:
FileNotFoundError: If any path in data_path does not exist.
"""
if isinstance(data_path, (str, Path)):
data_path = [data_path]

if video_type:
explicit_suffixes: set[str] | None = {f".{video_type.lstrip('.').lower()}"}
else:
explicit_suffixes = None
implicit_suffixes = {f".{ext.lower()}" for ext in auxfun_videos.SUPPORTED_VIDEOS}

videos: list[Path] = []
for path in map(Path, data_path):
if not path.exists():
raise FileNotFoundError(f"Could not find: {path}. Check access rights.")

if path.is_dir():
# Discriminate videos from other files; skip prior DLC outputs.
allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes
videos.extend(
f
for f in path.iterdir()
if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name
)
elif path.is_file():
# Accept all caller-supplied files; only filter if video_type set.
if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes:
videos.append(path)

# Resolve video paths and remove duplicates
unique_videos = list(dict.fromkeys(v.resolve() for v in videos))
if shuffle:
random.shuffle(unique_videos)
return unique_videos
return collect_video_paths(
data_path=data_path,
extensions=video_type,
shuffle=shuffle,
)


def ensure_multianimal_df_format(df_predictions: pd.DataFrame) -> pd.DataFrame:
Expand Down
3 changes: 2 additions & 1 deletion deeplabcut/pose_estimation_pytorch/apis/videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from deeplabcut.pose_estimation_pytorch.task import Task
from deeplabcut.refine_training_dataset.stitch import stitch_tracklets
from deeplabcut.utils import VideoReader, auxiliaryfunctions
from deeplabcut.utils.auxfun_videos import collect_video_paths


class VideoIterator(VideoReader):
Expand Down Expand Up @@ -540,7 +541,7 @@ def analyze_videos(
print(f"Using scorer: {dlc_scorer}")

# Reading video and init variables
videos = utils.list_videos_in_folder(videos, videotype, shuffle=in_random_order)
videos = collect_video_paths(videos, videotype, shuffle=in_random_order)
h5_files_created = False # Track if any .h5 files were created

for video in videos:
Expand Down
57 changes: 57 additions & 0 deletions deeplabcut/utils/auxfun_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@

import datetime
import os
import random
import subprocess
import warnings
from pathlib import Path

import cv2
import numpy as np
Expand Down Expand Up @@ -643,3 +645,58 @@ def display_help(*args):

plt.close(fig)
return bbox


def collect_video_paths(
data_path: str | Path | list[str | Path],
extensions: str | None = None,
shuffle: bool = False,
) -> list[Path]:
"""
Args:
data_path: Path or list of paths to folders containing videos, or individual
video files. Can be a mix of directories and files.
extensions: The types of videos to filter for (e.g., "mp4", ".mp4", ".avi", ".AVI", etc.).
- If set: filter all videos with the given extensions. Both for directory contents and supplied files.
- If ``None``, provided files are not filtered, but directory contents are filtered by ``SUPPORTED_VIDEOS``.
shuffle: Whether to shuffle the order of videos. If False, videos are returned
in sorted order for deterministic behavior.
Comment thread
deruyter92 marked this conversation as resolved.
Resolved

Returns:
The paths of videos to analyze. Duplicate paths are removed.

Raises:
FileNotFoundError: If any path in data_path does not exist.
"""
if isinstance(data_path, (str, Path)):
data_path = [data_path]

if extensions:
explicit_suffixes: set[str] | None = {f".{extensions.lstrip('.').lower()}"}
else:
explicit_suffixes = None
implicit_suffixes = {f".{ext.lower()}" for ext in SUPPORTED_VIDEOS}

videos: list[Path] = []
for path in map(Path, data_path):
if not path.exists():
raise FileNotFoundError(f"Could not find: {path}. Check access rights.")

if path.is_dir():
# Discriminate videos from other files; skip prior DLC outputs.
allowed = explicit_suffixes if explicit_suffixes else implicit_suffixes
videos.extend(
f
for f in path.iterdir()
Comment thread
deruyter92 marked this conversation as resolved.
Resolved
if f.is_file() and f.suffix.lower() in allowed and "_labeled." not in f.name and "_full." not in f.name
)
elif path.is_file():
# Accept all caller-supplied files; only filter if extensions set.
if explicit_suffixes is None or path.suffix.lower() in explicit_suffixes:
videos.append(path)

# Resolve video paths and remove duplicates
unique_videos = list(dict.fromkeys(v.resolve() for v in videos))
if shuffle:
random.shuffle(unique_videos)
return unique_videos
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#
# Licensed under GNU Lesser General Public License v3.0
#
"""Tests for ``list_videos_in_folder``.
"""Tests for ``collect_video_paths``.

These tests pin down the rule:

Expand All @@ -25,8 +25,7 @@

import pytest

from deeplabcut.pose_estimation_pytorch.apis.utils import list_videos_in_folder
from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS
from deeplabcut.utils.auxfun_videos import SUPPORTED_VIDEOS, collect_video_paths


def _touch(path: Path) -> Path:
Expand All @@ -41,7 +40,7 @@ def test_keeps_suffixless_files_when_explicitly_listed(tmp_path):
suffixed = _touch(tmp_path / "video.mp4")
hashed = _touch(tmp_path / "abcd1234")

result = list_videos_in_folder([suffixed, hashed], video_type=None)
result = collect_video_paths([suffixed, hashed], video_type=None)

assert {p.name for p in result} == {"video.mp4", "abcd1234"}

Expand All @@ -50,7 +49,7 @@ def test_accepts_path_objects_and_strings(tmp_path):
suffixed = _touch(tmp_path / "video.mp4")
hashed = _touch(tmp_path / "abcd1234")

result = list_videos_in_folder([str(suffixed), hashed], video_type=None)
result = collect_video_paths([str(suffixed), hashed], video_type=None)

assert {p.name for p in result} == {"video.mp4", "abcd1234"}

Expand All @@ -59,7 +58,7 @@ def test_accepts_single_path_argument(tmp_path):
"""A single path (not wrapped in a list) is also valid input."""
hashed = _touch(tmp_path / "abcd1234")

result = list_videos_in_folder(hashed, video_type=None)
result = collect_video_paths(hashed, video_type=None)

assert [p.name for p in result] == ["abcd1234"]

Expand All @@ -69,7 +68,7 @@ def test_explicit_video_type_filters_listed_files(tmp_path):
mp4 = _touch(tmp_path / "video.mp4")
avi = _touch(tmp_path / "video.avi")

result = list_videos_in_folder([mp4, avi], video_type="mp4")
result = collect_video_paths([mp4, avi], video_type="mp4")

assert [p.name for p in result] == ["video.mp4"]

Expand All @@ -78,7 +77,7 @@ def test_explicit_video_type_accepts_leading_dot(tmp_path):
mp4 = _touch(tmp_path / "video.mp4")
avi = _touch(tmp_path / "video.avi")

result = list_videos_in_folder([mp4, avi], video_type=".mp4")
result = collect_video_paths([mp4, avi], video_type=".mp4")

assert [p.name for p in result] == ["video.mp4"]

Expand All @@ -90,7 +89,7 @@ def test_directory_enumeration_filters_by_supported_videos(tmp_path):
_touch(tmp_path / "results.h5")
_touch(tmp_path / "abcd1234") # suffix-less file in a directory: not a video

result = list_videos_in_folder(tmp_path, video_type=None)
result = collect_video_paths(tmp_path, video_type=None)

assert [p.name for p in result] == [mp4.name]

Expand All @@ -101,7 +100,7 @@ def test_directory_enumeration_skips_dlc_artifacts(tmp_path):
_touch(tmp_path / "video_labeled.mp4")
_touch(tmp_path / "video_full.mp4")

result = list_videos_in_folder(tmp_path, video_type=None)
result = collect_video_paths(tmp_path, video_type=None)

assert [p.name for p in result] == [mp4.name]

Expand All @@ -115,7 +114,7 @@ def test_mixed_files_and_directories(tmp_path):
explicit_mp4 = _touch(tmp_path / "explicit.mp4")
explicit_hashed = _touch(tmp_path / "abcd1234")

result = list_videos_in_folder(
result = collect_video_paths(
[folder, explicit_mp4, explicit_hashed],
video_type=None,
)
Expand All @@ -130,22 +129,22 @@ def test_mixed_files_and_directories(tmp_path):
def test_duplicates_are_removed(tmp_path):
mp4 = _touch(tmp_path / "video.mp4")

result = list_videos_in_folder([mp4, mp4, str(mp4)], video_type=None)
result = collect_video_paths([mp4, mp4, str(mp4)], video_type=None)

assert len(result) == 1
assert result[0].name == "video.mp4"


def test_missing_path_raises(tmp_path):
with pytest.raises(FileNotFoundError):
list_videos_in_folder([tmp_path / "does_not_exist.mp4"], video_type=None)
collect_video_paths([tmp_path / "does_not_exist.mp4"], video_type=None)


@pytest.mark.parametrize("ext", SUPPORTED_VIDEOS)
def test_each_supported_extension_picked_up_in_directory(tmp_path, ext):
expected = _touch(tmp_path / f"clip.{ext}")

result = list_videos_in_folder(tmp_path, video_type=None)
result = collect_video_paths(tmp_path, video_type=None)

assert [p.name for p in result] == [expected.name]

Expand All @@ -157,6 +156,6 @@ def test_sorted_by_default_when_not_shuffled(tmp_path):

# The resolution order in the function is dict-insertion-stable; given a
# sorted input list we expect a sorted output list.
result = list_videos_in_folder([c, a, b], video_type=None, shuffle=False)
result = collect_video_paths([c, a, b], video_type=None, shuffle=False)

assert [p.name for p in result] == ["c.mp4", "a.mp4", "b.mp4"]
Loading