Skip to content

Commit b23dd7b

Browse files
authored
Merge pull request #26 from LemurPwned/feat/usability-improvements
Feat/usability improvements
2 parents 809c908 + 390485b commit b23dd7b

File tree

8 files changed

+185
-43
lines changed

8 files changed

+185
-43
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44

55
Changelog for the `video-sampler`.
66

7+
### 0.11.0
8+
9+
- added multiprocessing over multiple videos in the folder
10+
- new cli command & support reading from `.yaml` config files
11+
712
### 0.10.0
813

914
- added summary creation from sampled frames

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,16 @@ Documentation is available at [https://lemurpwned.github.io/video-sampler/](http
6767

6868
## Installation and Usage
6969

70+
If you intend to use all the integrations, you need all the dependencies:
71+
7072
```bash
71-
python3 -m pip install -U video_sampler
73+
python3 -m pip install -U video_sampler[all]
7274
```
7375

74-
If you intend to use all the integrations, you need can with all dependencies:
76+
for minimalist no-cli usage install:
7577

7678
```bash
77-
python3 -m pip install -U video_sampler[all]
79+
python3 -m pip install -U video_sampler
7880
```
7981

8082
Available extras are:
@@ -91,18 +93,22 @@ To see all available options, run:
9193
python3 -m video_sampler --help
9294
```
9395

94-
or simply
96+
### Basic usage
97+
98+
Plain:
9599

96100
```bash
97-
video_sampler --help
101+
python3 -m video_sampler hash FatCat.mp4 ./dataset-frames/ --hash-size 3 --buffer-size 20
98102
```
99103

100-
### Basic usage
104+
From the config file:
101105

102106
```bash
103-
python3 -m video_sampler hash FatCat.mp4 ./dataset-frames/ --hash-size 3 --buffer-size 20
107+
python3 -m video_sampler config ./configs/hash_base.yaml /my-video-folder/ ./my-output-folder
104108
```
105109

110+
You can set the number of workers to use with the `n_workers` parameter. The default is 1.
111+
106112
#### YT-DLP integration plugin
107113

108114
Before using please consult the ToS of the website you are scraping from -- use responsibly and for research purposes.

configs/hash_base.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Minimum time interval between processed frames (in seconds)
2+
min_frame_interval_sec: 3.0
3+
# Whether to process only keyframes (it's way faster than processing all frames)
4+
keyframes_only: true
5+
# Read interval while processing video (in seconds) (when there's no frame yielded, when to check again)
6+
queue_wait: 0.1
7+
debug: false
8+
# Whether to print stats
9+
print_stats: false
10+
# Buffer configuration
11+
buffer_config:
12+
type: hash
13+
# the smaller the hash size, the greater chance of collision
14+
# smaller hashsets are faster to process & reduce frames more aggressively
15+
hash_size: 8
16+
# size of the collision buffer. The larger the buffer, the more in time back the
17+
# hashes are stored.
18+
size: 15
19+
debug: true
20+
# Gating configuration
21+
gate_config:
22+
type: pass
23+
extractor_config: {}
24+
summary_config: {}
25+
# Number of workers (separate processes) to process the frames. Determines level of parallelism
26+
n_workers: 3

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "video_sampler"
33
description = "Video Sampler -- sample frames from a video file"
44
url = "https://github.com/LemurPwned/video-sampler"
5-
version = "0.10.2"
5+
version = "0.11.0"
66
authors = [
77
{ name = "LemurPwned", email = "lemurpwned@gmail.com" }
88
]
@@ -33,7 +33,8 @@ dependencies = [
3333
"rich >= 13.5.3",
3434
"typer[all] >= 0.9.0",
3535
"tqdm >= 4.66.1",
36-
"opencv-python-headless >= 4.9.0.80"
36+
"opencv-python-headless >= 4.9.0.80",
37+
"pydantic >= 2.6.1",
3738
]
3839

3940
[project.urls]

tests/test_ytplugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def test_keyword_extractor(subtitles):
2020
assert c == 4
2121

2222

23+
# skip
24+
@pytest.mark.skip
2325
def test_segment_sampler(random_video):
2426
ytdlp = YTDLPPlugin()
2527
title, url, subs = next(ytdlp.generate_urls(random_video, get_subs=True))
@@ -41,6 +43,7 @@ def test_segment_sampler(random_video):
4143
assert len(os.listdir(tempdir)) > 0
4244

4345

46+
@pytest.mark.skip
4447
def test_single_url_gen(random_video):
4548
ytdlp = YTDLPPlugin()
4649
title, url, subs = next(ytdlp.generate_urls(random_video, get_subs=True))
@@ -49,6 +52,7 @@ def test_single_url_gen(random_video):
4952
assert subs and len(subs) > 0, f"Expected subtitles, got {subs}"
5053

5154

55+
@pytest.mark.skip
5256
def test_search_url_gen():
5357
ytdlp = YTDLPPlugin()
5458
expected_results, results = 5, 0

video_sampler/__main__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,9 @@ def main(
123123
summary_interval: int = typer.Option(
124124
-1, help="Interval in seconds to summarise the video."
125125
),
126+
n_workers: int = typer.Option(
127+
1, help="Number of workers to use. Default is 1. Use -1 to use all CPUs."
128+
),
126129
) -> None:
127130
"""Default buffer is the perceptual hash buffer"""
128131
extractor_cfg = {}
@@ -169,6 +172,7 @@ def main(
169172
}
170173
),
171174
extractor_config=extractor_cfg,
175+
n_workers=n_workers,
172176
)
173177
if ytdlp:
174178
video_path = _ytdlp_plugin(yt_extra_args, video_path, get_subs=subs_enable)
@@ -210,6 +214,9 @@ def buffer(
210214
yt_extra_args: str = typer.Option(
211215
None, help="Extra arguments for YouTube-DLP extraction in classic format."
212216
),
217+
n_workers: int = typer.Option(
218+
1, help="Number of workers to use. Default is 1. Use -1 to use all CPUs."
219+
),
213220
):
214221
"""Buffer type can be one of entropy, gzip, hash, passthrough"""
215222
cfg = SamplerConfig(
@@ -239,6 +246,7 @@ def buffer(
239246
"type": "pass",
240247
}
241248
),
249+
n_workers=n_workers,
242250
)
243251
if ytdlp:
244252
video_path = _ytdlp_plugin(yt_extra_args, video_path)
@@ -278,6 +286,9 @@ def clip(
278286
yt_extra_args: str = typer.Option(
279287
None, help="Extra arguments for YouTube-DLP extraction in classic format."
280288
),
289+
n_workers: int = typer.Option(
290+
1, help="Number of workers to use. Default is 1. Use -1 to use all CPUs."
291+
),
281292
):
282293
"""Buffer type can be only of type hash when using CLIP gating."""
283294
if pos_samples is not None:
@@ -309,12 +320,37 @@ def clip(
309320
"model_name": model_name,
310321
"batch_size": batch_size,
311322
},
323+
n_workers=n_workers,
312324
)
313325
if ytdlp:
314326
video_path = _ytdlp_plugin(yt_extra_args, video_path)
315327
_create_from_config(cfg=cfg, video_path=video_path, output_path=output_path)
316328

317329

330+
@app.command(name="config")
331+
def from_config(
332+
config_path: str = typer.Argument(..., help="Path to the configuration file."),
333+
video_path: str = typer.Argument(
334+
..., help="Path to the video file or a glob pattern."
335+
),
336+
output_path: str = typer.Argument(..., help="Path to the output folder."),
337+
ytdlp: bool = typer.Option(
338+
False,
339+
help="Use yt-dlp to download videos from urls. Default is False."
340+
" Enabling this will treat video_path as an input to ytdlp command.",
341+
),
342+
yt_extra_args: str = typer.Option(
343+
None, help="Extra arguments for YouTube-DLP extraction in classic format."
344+
),
345+
):
346+
"""Create a sampler from a configuration file."""
347+
348+
cfg = SamplerConfig.from_yaml(config_path)
349+
if ytdlp:
350+
video_path = _ytdlp_plugin(yt_extra_args, video_path)
351+
_create_from_config(cfg=cfg, video_path=video_path, output_path=output_path)
352+
353+
318354
def main_loop():
319355
app()
320356

video_sampler/buffer.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
from abc import ABC, abstractmethod
44
from collections import OrderedDict
55
from collections.abc import Iterable
6-
from dataclasses import asdict, dataclass, field
6+
from dataclasses import asdict, field
77
from typing import Any
88

9+
import yaml
910
from imagehash import average_hash, phash
1011
from PIL import Image
12+
from pydantic import BaseModel, Field
1113

1214
from .logging import Color, console
1315

1416

15-
@dataclass
16-
class SamplerConfig:
17+
class SamplerConfig(BaseModel):
1718
"""
1819
Configuration options for the video sampler.
1920
@@ -43,9 +44,9 @@ class SamplerConfig:
4344
4445
"""
4546

46-
min_frame_interval_sec: float = 1
47+
min_frame_interval_sec: float = Field(default=1, ge=0)
4748
keyframes_only: bool = True
48-
queue_wait: float = 0.1
49+
queue_wait: float = Field(default=0.1, ge=1e-3)
4950
debug: bool = False
5051
print_stats: bool = False
5152
buffer_config: dict[str, Any] = field(
@@ -63,10 +64,17 @@ class SamplerConfig:
6364
)
6465
extractor_config: dict[str, Any] = field(default_factory=dict)
6566
summary_config: dict[str, Any] = field(default_factory=dict)
67+
n_workers: int = 1
6668

6769
def __str__(self) -> str:
6870
return str(asdict(self))
6971

72+
@classmethod
73+
def from_yaml(cls, file_path: str) -> "SamplerConfig":
74+
with open(file_path) as file:
75+
data = yaml.safe_load(file)
76+
return cls(**data)
77+
7078

7179
class FrameBuffer(ABC):
7280
@abstractmethod

0 commit comments

Comments
 (0)