Update core/clip_extractor.py
Browse files- core/clip_extractor.py +47 -7
core/clip_extractor.py
CHANGED
|
@@ -123,14 +123,16 @@ class ClipExtractor:
|
|
| 123 |
candidates: List[ClipCandidate],
|
| 124 |
num_clips: int,
|
| 125 |
enforce_diversity: bool = True,
|
|
|
|
| 126 |
) -> List[ClipCandidate]:
|
| 127 |
"""
|
| 128 |
-
Select top clips from candidates.
|
| 129 |
|
| 130 |
Args:
|
| 131 |
candidates: List of clip candidates with scores
|
| 132 |
num_clips: Number of clips to select
|
| 133 |
enforce_diversity: Enforce minimum gap between clips
|
|
|
|
| 134 |
|
| 135 |
Returns:
|
| 136 |
List of selected ClipCandidate objects
|
|
@@ -147,9 +149,23 @@ class ClipExtractor:
|
|
| 147 |
if not enforce_diversity:
|
| 148 |
return sorted_candidates[:num_clips]
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# Select with diversity constraint
|
| 151 |
selected = []
|
| 152 |
-
min_gap = self.config.min_gap_between_clips
|
| 153 |
|
| 154 |
for candidate in sorted_candidates:
|
| 155 |
if len(selected) >= num_clips:
|
|
@@ -167,11 +183,33 @@ class ClipExtractor:
|
|
| 167 |
if is_diverse:
|
| 168 |
selected.append(candidate)
|
| 169 |
|
| 170 |
-
# If we couldn't get enough with diversity, relax
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
if len(selected) < num_clips:
|
| 172 |
logger.warning(
|
| 173 |
f"Only {len(selected)} diverse clips found, "
|
| 174 |
-
f"
|
| 175 |
)
|
| 176 |
for candidate in sorted_candidates:
|
| 177 |
if candidate not in selected:
|
|
@@ -179,7 +217,7 @@ class ClipExtractor:
|
|
| 179 |
if len(selected) >= num_clips:
|
| 180 |
break
|
| 181 |
|
| 182 |
-
logger.info(f"Selected {len(selected)} clips from {len(candidates)} candidates")
|
| 183 |
return selected
|
| 184 |
|
| 185 |
def adjust_to_scene_boundaries(
|
|
@@ -246,6 +284,7 @@ class ClipExtractor:
|
|
| 246 |
num_clips: Optional[int] = None,
|
| 247 |
generate_thumbnails: bool = True,
|
| 248 |
reencode: bool = False,
|
|
|
|
| 249 |
) -> List[ExtractedClip]:
|
| 250 |
"""
|
| 251 |
Extract clips from video.
|
|
@@ -257,6 +296,7 @@ class ClipExtractor:
|
|
| 257 |
num_clips: Number of clips to extract (None = use config default)
|
| 258 |
generate_thumbnails: Whether to generate thumbnails
|
| 259 |
reencode: Whether to re-encode clips (slower but precise)
|
|
|
|
| 260 |
|
| 261 |
Returns:
|
| 262 |
List of ExtractedClip objects
|
|
@@ -266,8 +306,8 @@ class ClipExtractor:
|
|
| 266 |
num_clips = num_clips or self.config.default_num_clips
|
| 267 |
|
| 268 |
with LogTimer(logger, f"Extracting {num_clips} clips"):
|
| 269 |
-
# Select top clips
|
| 270 |
-
selected = self.select_clips(candidates, num_clips)
|
| 271 |
|
| 272 |
if not selected:
|
| 273 |
logger.warning("No clips to extract")
|
|
|
|
| 123 |
candidates: List[ClipCandidate],
|
| 124 |
num_clips: int,
|
| 125 |
enforce_diversity: bool = True,
|
| 126 |
+
video_duration: Optional[float] = None,
|
| 127 |
) -> List[ClipCandidate]:
|
| 128 |
"""
|
| 129 |
+
Select top clips from candidates with adaptive diversity.
|
| 130 |
|
| 131 |
Args:
|
| 132 |
candidates: List of clip candidates with scores
|
| 133 |
num_clips: Number of clips to select
|
| 134 |
enforce_diversity: Enforce minimum gap between clips
|
| 135 |
+
video_duration: Total video duration for adaptive gap calculation
|
| 136 |
|
| 137 |
Returns:
|
| 138 |
List of selected ClipCandidate objects
|
|
|
|
| 149 |
if not enforce_diversity:
|
| 150 |
return sorted_candidates[:num_clips]
|
| 151 |
|
| 152 |
+
# Calculate adaptive minimum gap based on video duration
|
| 153 |
+
# For longer videos, we want clips spread across the entire video
|
| 154 |
+
if video_duration and video_duration > 0:
|
| 155 |
+
# Target: clips should be spread across the video
|
| 156 |
+
# Divide video into (num_clips + 1) sections, min_gap is section size
|
| 157 |
+
adaptive_gap = video_duration / (num_clips + 1)
|
| 158 |
+
# But don't go below config minimum or above 1/3 of video
|
| 159 |
+
min_gap = max(
|
| 160 |
+
self.config.min_gap_between_clips,
|
| 161 |
+
min(adaptive_gap, video_duration / 3)
|
| 162 |
+
)
|
| 163 |
+
logger.debug(f"Adaptive min_gap: {min_gap:.1f}s for {video_duration:.1f}s video")
|
| 164 |
+
else:
|
| 165 |
+
min_gap = self.config.min_gap_between_clips
|
| 166 |
+
|
| 167 |
# Select with diversity constraint
|
| 168 |
selected = []
|
|
|
|
| 169 |
|
| 170 |
for candidate in sorted_candidates:
|
| 171 |
if len(selected) >= num_clips:
|
|
|
|
| 183 |
if is_diverse:
|
| 184 |
selected.append(candidate)
|
| 185 |
|
| 186 |
+
# If we couldn't get enough with strict diversity, progressively relax
|
| 187 |
+
if len(selected) < num_clips:
|
| 188 |
+
# Try with 50% of the gap
|
| 189 |
+
relaxed_gap = min_gap * 0.5
|
| 190 |
+
logger.info(f"Relaxing diversity to {relaxed_gap:.1f}s gap")
|
| 191 |
+
|
| 192 |
+
for candidate in sorted_candidates:
|
| 193 |
+
if len(selected) >= num_clips:
|
| 194 |
+
break
|
| 195 |
+
if candidate in selected:
|
| 196 |
+
continue
|
| 197 |
+
|
| 198 |
+
is_diverse = True
|
| 199 |
+
for existing in selected:
|
| 200 |
+
gap = abs(candidate.start_time - existing.start_time)
|
| 201 |
+
if gap < relaxed_gap:
|
| 202 |
+
is_diverse = False
|
| 203 |
+
break
|
| 204 |
+
|
| 205 |
+
if is_diverse:
|
| 206 |
+
selected.append(candidate)
|
| 207 |
+
|
| 208 |
+
# Final fallback: just take top clips if still not enough
|
| 209 |
if len(selected) < num_clips:
|
| 210 |
logger.warning(
|
| 211 |
f"Only {len(selected)} diverse clips found, "
|
| 212 |
+
f"filling remaining from top candidates"
|
| 213 |
)
|
| 214 |
for candidate in sorted_candidates:
|
| 215 |
if candidate not in selected:
|
|
|
|
| 217 |
if len(selected) >= num_clips:
|
| 218 |
break
|
| 219 |
|
| 220 |
+
logger.info(f"Selected {len(selected)} clips from {len(candidates)} candidates (gap={min_gap:.1f}s)")
|
| 221 |
return selected
|
| 222 |
|
| 223 |
def adjust_to_scene_boundaries(
|
|
|
|
| 284 |
num_clips: Optional[int] = None,
|
| 285 |
generate_thumbnails: bool = True,
|
| 286 |
reencode: bool = False,
|
| 287 |
+
video_duration: Optional[float] = None,
|
| 288 |
) -> List[ExtractedClip]:
|
| 289 |
"""
|
| 290 |
Extract clips from video.
|
|
|
|
| 296 |
num_clips: Number of clips to extract (None = use config default)
|
| 297 |
generate_thumbnails: Whether to generate thumbnails
|
| 298 |
reencode: Whether to re-encode clips (slower but precise)
|
| 299 |
+
video_duration: Total video duration for diversity calculation
|
| 300 |
|
| 301 |
Returns:
|
| 302 |
List of ExtractedClip objects
|
|
|
|
| 306 |
num_clips = num_clips or self.config.default_num_clips
|
| 307 |
|
| 308 |
with LogTimer(logger, f"Extracting {num_clips} clips"):
|
| 309 |
+
# Select top clips with adaptive diversity
|
| 310 |
+
selected = self.select_clips(candidates, num_clips, video_duration=video_duration)
|
| 311 |
|
| 312 |
if not selected:
|
| 313 |
logger.warning("No clips to extract")
|