embed images
This commit is contained in:
@@ -16,17 +16,19 @@ logger = logging.getLogger(__name__)
|
||||
class FrameExtractor:
|
||||
"""Extract frames from video files."""
|
||||
|
||||
def __init__(self, video_path: str, output_dir: str = "frames"):
|
||||
def __init__(self, video_path: str, output_dir: str = "frames", quality: int = 75):
|
||||
"""
|
||||
Initialize frame extractor.
|
||||
|
||||
Args:
|
||||
video_path: Path to video file
|
||||
output_dir: Directory to save extracted frames
|
||||
quality: JPEG quality for saved frames (0-100)
|
||||
"""
|
||||
self.video_path = video_path
|
||||
self.output_dir = Path(output_dir)
|
||||
self.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.quality = quality
|
||||
|
||||
def extract_by_interval(self, interval_seconds: int = 5) -> List[Tuple[str, float]]:
|
||||
"""
|
||||
@@ -56,8 +58,16 @@ class FrameExtractor:
|
||||
frame_filename = f"frame_{saved_count:05d}_{timestamp:.2f}s.jpg"
|
||||
frame_path = self.output_dir / frame_filename
|
||||
|
||||
# Use high quality for text readability (95 = high quality JPEG)
|
||||
cv2.imwrite(str(frame_path), frame, [cv2.IMWRITE_JPEG_QUALITY, 95])
|
||||
# Downscale to 1600px width for smaller file size (but still readable)
|
||||
height, width = frame.shape[:2]
|
||||
if width > 1600:
|
||||
ratio = 1600 / width
|
||||
new_width = 1600
|
||||
new_height = int(height * ratio)
|
||||
frame = cv2.resize(frame, (new_width, new_height), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
# Save with configured quality (matches embed quality)
|
||||
cv2.imwrite(str(frame_path), frame, [cv2.IMWRITE_JPEG_QUALITY, self.quality])
|
||||
frames_info.append((str(frame_path), timestamp))
|
||||
saved_count += 1
|
||||
|
||||
@@ -90,16 +100,24 @@ class FrameExtractor:
|
||||
output_pattern = self.output_dir / f"{video_name}_%05d.jpg"
|
||||
|
||||
try:
|
||||
# Use FFmpeg's scene detection filter with high quality output
|
||||
# Use FFmpeg's scene detection filter with downscaling
|
||||
stream = ffmpeg.input(self.video_path)
|
||||
stream = ffmpeg.filter(stream, 'select', f'gt(scene,{threshold/100})')
|
||||
stream = ffmpeg.filter(stream, 'showinfo')
|
||||
# Scale to 1600px width (maintains aspect ratio, still readable)
|
||||
# Use simple conditional: if width > 1600, scale to 1600, else keep original
|
||||
stream = ffmpeg.filter(stream, 'scale', w='min(1600,iw)', h=-1)
|
||||
|
||||
# Convert JPEG quality (0-100) to FFmpeg qscale (2-31, lower=better)
|
||||
# Rough mapping: qscale ≈ (100 - quality) / 10, clamped to 2-31
|
||||
qscale = max(2, min(31, int((100 - self.quality) / 10 + 2)))
|
||||
|
||||
stream = ffmpeg.output(
|
||||
stream,
|
||||
str(output_pattern),
|
||||
vsync='vfr',
|
||||
frame_pts=1,
|
||||
**{'q:v': '2'} # High quality JPEG
|
||||
**{'q:v': str(qscale)} # Matches configured quality
|
||||
)
|
||||
|
||||
# Run with stderr capture to get showinfo output
|
||||
|
||||
Reference in New Issue
Block a user