distant-frames 0.2.2__tar.gz → 0.3.0__tar.gz

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.

Potentially problematic release.


This version of distant-frames might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: distant-frames
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Smart video frame extraction tool
5
5
  Project-URL: Homepage, https://github.com/yubraaj11/distant-frames
6
6
  Project-URL: Repository, https://github.com/yubraaj11/distant-frames
@@ -38,6 +38,8 @@ Description-Content-Type: text/markdown
38
38
  - **Histogram Correlation**: Uses HSV color space histogram comparison for robust similarity detection.
39
39
  - **Configurable Threshold**: Fine-tune the sensitivity of frame dropping to suit your specific video content.
40
40
  - **Efficient Processing**: Seeks directly to target timestamps (`CAP_PROP_POS_FRAMES`) for faster processing than frame-by-frame reading.
41
+ - **Custom Start Time**: Begin extraction from any point in the video using a timestamp in seconds.
42
+ - **Open Eyes Filter**: Optionally keep only frames where at least one face with both eyes open is detected, using local Haar cascade classifiers.
41
43
 
42
44
  ## 🛠️ Prerequisites
43
45
 
@@ -89,7 +91,9 @@ distant-frames path/to/video.mp4 -o path/to/output -t 0.75
89
91
  |----------|-------------|---------|
90
92
  | `video_path` | Path to the input video file (Required). | N/A |
91
93
  | `--output`, `-o` | Directory to save the extracted frames. | `extracted_frames` |
92
- | `--threshold`, `-t` | Defines the similarity score threshold (0.0 to 1.0) between frames. If the similarity score is **higher** than this value, the frame will be discarded. | `0.65` |
94
+ | `--threshold`, `-t` | Similarity score threshold (0.0 to 1.0). Frames with a score **higher** than this value are discarded. | `0.65` |
95
+ | `--start`, `-s` | Timestamp in seconds to begin extraction from. | `0.0` |
96
+ | `--open-eyes` | When set, only saves frames where at least one face with both eyes open is detected. | Off |
93
97
 
94
98
  ### Examples
95
99
 
@@ -103,14 +107,30 @@ distant-frames my_vacation.mp4
103
107
  distant-frames my_vacation.mp4 -o best_shots -t 0.95
104
108
  ```
105
109
 
110
+ **Start extraction from a specific timestamp (e.g. 1 minute 30 seconds in):**
111
+ ```bash
112
+ distant-frames interview.mp4 -s 90
113
+ ```
114
+
115
+ **Only keep frames where a person's eyes are open:**
116
+ ```bash
117
+ distant-frames interview.mp4 --open-eyes -o key_frames
118
+ ```
119
+
120
+ **Combine all options:**
121
+ ```bash
122
+ distant-frames interview.mp4 -s 90 -t 0.80 --open-eyes -o key_frames
123
+ ```
124
+
106
125
  ## 🔍 How It Works
107
126
 
108
- 1. **Sampling**: The script checks one frame every second (based on the video's FPS).
127
+ 1. **Sampling**: The script checks one frame every second (based on the video's FPS), starting from `--start` if provided.
109
128
  2. **Comparison**: It compares the current candidate frame against the **last successfully saved frame**.
110
129
  3. **Algorithm**: It converts frames to HSV color space and calculates Normalized Histogram Correlation.
111
130
  4. **Decision**:
112
- - If similarity < `threshold`: **SAVE** (The scene has changed).
131
+ - If similarity < `threshold`: candidate for saving.
113
132
  - If similarity >= `threshold`: **SKIP** (The scene is too similar).
133
+ 5. **Open Eyes Filter** *(optional)*: If `--open-eyes` is set, a candidate frame is only saved if a face with two open eyes is detected using Haar cascade classifiers.
114
134
 
115
135
  ## 🧪 Testing
116
136
 
@@ -14,6 +14,8 @@
14
14
  - **Histogram Correlation**: Uses HSV color space histogram comparison for robust similarity detection.
15
15
  - **Configurable Threshold**: Fine-tune the sensitivity of frame dropping to suit your specific video content.
16
16
  - **Efficient Processing**: Seeks directly to target timestamps (`CAP_PROP_POS_FRAMES`) for faster processing than frame-by-frame reading.
17
+ - **Custom Start Time**: Begin extraction from any point in the video using a timestamp in seconds.
18
+ - **Open Eyes Filter**: Optionally keep only frames where at least one face with both eyes open is detected, using local Haar cascade classifiers.
17
19
 
18
20
  ## 🛠️ Prerequisites
19
21
 
@@ -65,7 +67,9 @@ distant-frames path/to/video.mp4 -o path/to/output -t 0.75
65
67
  |----------|-------------|---------|
66
68
  | `video_path` | Path to the input video file (Required). | N/A |
67
69
  | `--output`, `-o` | Directory to save the extracted frames. | `extracted_frames` |
68
- | `--threshold`, `-t` | Defines the similarity score threshold (0.0 to 1.0) between frames. If the similarity score is **higher** than this value, the frame will be discarded. | `0.65` |
70
+ | `--threshold`, `-t` | Similarity score threshold (0.0 to 1.0). Frames with a score **higher** than this value are discarded. | `0.65` |
71
+ | `--start`, `-s` | Timestamp in seconds to begin extraction from. | `0.0` |
72
+ | `--open-eyes` | When set, only saves frames where at least one face with both eyes open is detected. | Off |
69
73
 
70
74
  ### Examples
71
75
 
@@ -79,14 +83,30 @@ distant-frames my_vacation.mp4
79
83
  distant-frames my_vacation.mp4 -o best_shots -t 0.95
80
84
  ```
81
85
 
86
+ **Start extraction from a specific timestamp (e.g. 1 minute 30 seconds in):**
87
+ ```bash
88
+ distant-frames interview.mp4 -s 90
89
+ ```
90
+
91
+ **Only keep frames where a person's eyes are open:**
92
+ ```bash
93
+ distant-frames interview.mp4 --open-eyes -o key_frames
94
+ ```
95
+
96
+ **Combine all options:**
97
+ ```bash
98
+ distant-frames interview.mp4 -s 90 -t 0.80 --open-eyes -o key_frames
99
+ ```
100
+
82
101
  ## 🔍 How It Works
83
102
 
84
- 1. **Sampling**: The script checks one frame every second (based on the video's FPS).
103
+ 1. **Sampling**: The script checks one frame every second (based on the video's FPS), starting from `--start` if provided.
85
104
  2. **Comparison**: It compares the current candidate frame against the **last successfully saved frame**.
86
105
  3. **Algorithm**: It converts frames to HSV color space and calculates Normalized Histogram Correlation.
87
106
  4. **Decision**:
88
- - If similarity < `threshold`: **SAVE** (The scene has changed).
107
+ - If similarity < `threshold`: candidate for saving.
89
108
  - If similarity >= `threshold`: **SKIP** (The scene is too similar).
109
+ 5. **Open Eyes Filter** *(optional)*: If `--open-eyes` is set, a candidate frame is only saved if a face with two open eyes is detected using Haar cascade classifiers.
90
110
 
91
111
  ## 🧪 Testing
92
112
 
@@ -35,15 +35,30 @@ def main(
35
35
  max=1.0,
36
36
  help="Similarity threshold (0.0-1.0). Higher values mean stricter deduplication (fewer frames saved)."
37
37
  )
38
- ] = 0.65,
38
+ ] = 0.75,
39
+ start_time: Annotated[
40
+ float,
41
+ typer.Option(
42
+ "--start", "-s",
43
+ min=0.0,
44
+ help="Start extraction from this timestamp (in seconds). Defaults to 0 (beginning of video)."
45
+ )
46
+ ] = 0.0,
47
+ open_eyes_only: Annotated[
48
+ bool,
49
+ typer.Option(
50
+ "--open-eyes",
51
+ help="Only save frames where at least one face with both eyes open is detected."
52
+ )
53
+ ] = False,
39
54
  ):
40
55
  """
41
56
  Extract distinct frames from a video file based on visual similarity.
42
-
57
+
43
58
  The tool samples the video at 1-second intervals and compares consecutive frames.
44
59
  Frames that are too similar to previously saved frames are automatically skipped.
45
60
  """
46
- extract_frames(str(video_path), output, threshold)
61
+ extract_frames(str(video_path), output, threshold, start_time, open_eyes_only)
47
62
 
48
63
  if __name__ == "__main__":
49
64
  app()
@@ -3,6 +3,32 @@ import os
3
3
  from pathlib import Path
4
4
  import uuid
5
5
 
6
+ _CASCADES_DIR = Path(__file__).parent.parent / "haarcascade_classifiers"
7
+
8
+ def _load_eye_cascades():
9
+ """Load and return (face_cascade, eye_cascade) from the local haarcascade_classifiers/ directory."""
10
+ face_path = str(_CASCADES_DIR / "haarcascade_frontalface_default.xml")
11
+ eye_path = str(_CASCADES_DIR / "haarcascade_eye.xml")
12
+ face_cascade = cv2.CascadeClassifier(face_path)
13
+ eye_cascade = cv2.CascadeClassifier(eye_path)
14
+ if face_cascade.empty() or eye_cascade.empty():
15
+ raise RuntimeError(
16
+ f"Failed to load Haar cascade classifiers from {_CASCADES_DIR}. "
17
+ "Ensure haarcascade_frontalface_default.xml and haarcascade_eye.xml exist there."
18
+ )
19
+ return face_cascade, eye_cascade
20
+
21
+ def has_open_eyes(frame, face_cascade, eye_cascade):
22
+ """Return True if at least one face with two detected (open) eyes is found in the frame."""
23
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
24
+ faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
25
+ for (x, y, w, h) in faces:
26
+ roi = gray[y:y + h, x:x + w]
27
+ eyes = eye_cascade.detectMultiScale(roi, scaleFactor=1.1, minNeighbors=5)
28
+ if len(eyes) >= 2:
29
+ return True
30
+ return False
31
+
6
32
  def calculate_similarity(frame1, frame2):
7
33
  """Calculates similarity between two frames using Histogram Correlation.
8
34
 
@@ -34,13 +60,14 @@ def calculate_similarity(frame1, frame2):
34
60
  similarity = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
35
61
  return similarity
36
62
 
37
- def extract_frames(video_path, output_folder, threshold=0.65):
63
+ def extract_frames(video_path, output_folder, threshold=0.65, start_time=0.0, open_eyes_only=False):
38
64
  """Extracts distinct frames from a video file based on visual similarity.
39
65
 
40
- The function samples the video at 1-second intervals. It compares the current
41
- frame with the last saved frame. If the similarity score is below the
42
- specified threshold, the frame is considered distinct and saved.
43
-
66
+ The function samples the video at 1-second intervals starting from
67
+ `start_time`. It compares the current frame with the last saved frame.
68
+ If the similarity score is below the specified threshold, the frame is
69
+ considered distinct and saved.
70
+
44
71
  If a frame is skipped (similar to the last saved frame), the next comparison
45
72
  will be performed against the *previous* saved frame (if available) to ensure
46
73
  robustness against gradual changes or local similarities.
@@ -51,8 +78,12 @@ def extract_frames(video_path, output_folder, threshold=0.65):
51
78
  The directory will be created if it does not exist.
52
79
  threshold (float, optional): Similarity threshold (0.0 to 1.0).
53
80
  Frames with similarity higher than this value regarding the last
54
- saved frame will be dropped. Higher values mean stricter
55
- deduplication (fewer frames saved). Defaults to 0.65.
81
+ saved frame will be dropped. Defaults to 0.65.
82
+ start_time (float, optional): Timestamp in seconds from which to begin
83
+ extraction. Defaults to 0.0 (beginning of video).
84
+ open_eyes_only (bool, optional): When True, only frames where at least
85
+ one face with two open eyes is detected are saved. Uses OpenCV
86
+ Haar cascade classifiers. Defaults to False.
56
87
 
57
88
  Returns:
58
89
  None
@@ -64,6 +95,8 @@ def extract_frames(video_path, output_folder, threshold=0.65):
64
95
  if not os.path.exists(output_folder):
65
96
  os.makedirs(output_folder)
66
97
 
98
+ face_cascade, eye_cascade = (_load_eye_cascades() if open_eyes_only else (None, None))
99
+
67
100
  video_file_name = Path(video_path).stem
68
101
  cap = cv2.VideoCapture(str(video_path))
69
102
  if not cap.isOpened():
@@ -75,12 +108,19 @@ def extract_frames(video_path, output_folder, threshold=0.65):
75
108
  print("Error: Could not retrieve FPS.")
76
109
  return
77
110
 
78
- print(f"Video FPS: {fps}")
79
-
111
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
112
+ duration = total_frames / fps
113
+ if start_time >= duration:
114
+ print(f"Error: start_time ({start_time:.1f}s) is beyond the video duration ({duration:.1f}s).")
115
+ cap.release()
116
+ return
117
+
118
+ print(f"Video FPS: {fps} | Duration: {duration:.1f}s | Starting at: {start_time:.1f}s")
119
+
80
120
  # We want to check frames every 1 second
81
121
  frame_interval = int(fps)
82
-
83
- current_frame_idx = 0
122
+
123
+ current_frame_idx = int(start_time * fps)
84
124
  saved_count = 0
85
125
  last_saved_frame = None
86
126
  last_saved_timestamp = None
@@ -131,6 +171,11 @@ def extract_frames(video_path, output_folder, threshold=0.65):
131
171
  skip_reference_frame = last_saved_frame
132
172
  skip_reference_timestamp = last_saved_timestamp
133
173
 
174
+ if should_save and open_eyes_only:
175
+ if not has_open_eyes(frame, face_cascade, eye_cascade):
176
+ print(f"[{timestamp:.1f}s] Open-eyes check failed → SKIP")
177
+ should_save = False
178
+
134
179
  if should_save:
135
180
  output_filename = os.path.join(output_folder, f"{video_file_name}_frame_{uuid.uuid4().hex[:8]}.jpg")
136
181
  cv2.imwrite(output_filename, frame)