From 8fece16ca785074108fb9c3b93c0aacad33d22c7 Mon Sep 17 00:00:00 2001 From: yair-mv Date: Sat, 8 Nov 2025 12:26:11 +0200 Subject: [PATCH] Add timestamp overlay feature for video mode - Add --timestamp / --ts flag to embed frame count on bottom left corner - Implement add_timestamp_overlay() function with large, visible text - Yellow text on black background for good visibility - Shows 'Frame: X/Y' format with current and total frame counts - Works with both column and row video modes - Applied after rotation for proper positioning - Update documentation with timestamp examples and parameter descriptions - Tested successfully with sample video files --- main.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++---- readme.md | 7 ++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index f924924..35e19ce 100644 --- a/main.py +++ b/main.py @@ -475,7 +475,49 @@ def extract_row_strip(video_path, y_row, output_path, change_threshold=0.01, rel cv2.imwrite(str(output_path), strip_image) -def extract_column_strip_video(video_path, x_column, output_path, change_threshold=0.005, relax=0, start_frame=0, end_frame=None, fps=30): +def add_timestamp_overlay(frame, frame_count, total_frames): + """ + Add frame count overlay to the bottom left corner of the frame. + + Args: + frame: The video frame to add overlay to + frame_count: Current frame number (1-based) + total_frames: Total number of frames + + Returns: + Frame with timestamp overlay + """ + overlay = frame.copy() + font = cv2.FONT_HERSHEY_SIMPLEX + font_scale = 1.2 + font_thickness = 2 + text_color = (0, 255, 255) # Yellow for visibility + bg_color = (0, 0, 0) # Black background + + # Create timestamp text + timestamp_text = f"Frame: {frame_count}/{total_frames}" + + # Get text size for background rectangle + (text_width, text_height), baseline = cv2.getTextSize(timestamp_text, font, font_scale, font_thickness) + + # Position at bottom left with some padding + x_pos = 10 + y_pos = frame.shape[0] - 10 # Bottom of frame minus padding + + # Draw background rectangle + cv2.rectangle(overlay, + (x_pos - 5, y_pos - text_height - baseline - 5), + (x_pos + text_width + 5, y_pos + baseline + 5), + bg_color, -1) + + # Draw text + cv2.putText(overlay, timestamp_text, (x_pos, y_pos - baseline), + font, font_scale, text_color, font_thickness, cv2.LINE_AA) + + return overlay + + +def extract_column_strip_video(video_path, x_column, output_path, change_threshold=0.005, relax=0, start_frame=0, end_frame=None, fps=30, timestamp=False): """ Extract vertical strip at x_column from each frame and create an MJPEG video. Each frame of the output video shows the accumulated scan lines up to that point. @@ -489,6 +531,7 @@ def extract_column_strip_video(video_path, x_column, output_path, change_thresho start_frame: First frame to process (0-based) end_frame: Last frame to process (None = until end) fps: Output video frame rate + timestamp: If True, embed frame count on bottom left corner """ cap = cv2.VideoCapture(str(video_path)) @@ -611,6 +654,10 @@ def extract_column_strip_video(video_path, x_column, output_path, change_thresho # Convert to numpy array and create the frame strip_frame = np.stack(accumulated_columns, axis=1) + # Add timestamp overlay if requested + if timestamp: + strip_frame = add_timestamp_overlay(strip_frame, frame_idx + 1, len(significant_columns)) + # Write frame to video out.write(strip_frame) @@ -625,7 +672,7 @@ def extract_column_strip_video(video_path, x_column, output_path, change_thresho print(f"Total duration: {len(significant_columns)/fps:.2f} seconds") -def extract_row_strip_video(video_path, y_row, output_path, change_threshold=0.01, relax=0, start_frame=0, end_frame=None, fps=30): +def extract_row_strip_video(video_path, y_row, output_path, change_threshold=0.01, relax=0, start_frame=0, end_frame=None, fps=30, timestamp=False): """ Extract horizontal strip at y_row from each frame and create an MJPEG video. Each frame of the output video shows the accumulated scan lines up to that point. @@ -639,6 +686,7 @@ def extract_row_strip_video(video_path, y_row, output_path, change_threshold=0.0 start_frame: First frame to process (0-based) end_frame: Last frame to process (None = until end) fps: Output video frame rate + timestamp: If True, embed frame count on bottom left corner """ cap = cv2.VideoCapture(str(video_path)) @@ -766,6 +814,10 @@ def extract_row_strip_video(video_path, y_row, output_path, change_threshold=0.0 # Rotate counter-clockwise 90 degrees to match image mode orientation strip_frame = cv2.rotate(strip_frame, cv2.ROTATE_90_COUNTERCLOCKWISE) + # Add timestamp overlay if requested (after rotation) + if timestamp: + strip_frame = add_timestamp_overlay(strip_frame, frame_idx + 1, len(significant_rows)) + # Write frame to video out.write(strip_frame) @@ -862,6 +914,13 @@ def main(): help="Output video frame rate (default: 30.0, only used with --video)" ) + parser.add_argument( + "--timestamp", + "--ts", + action="store_true", + help="Embed frame count on bottom left corner (video mode only)" + ) + args = parser.parse_args() # Validate input file @@ -973,11 +1032,11 @@ def main(): if args.xcolumn is not None: print(f"Column mode: Extracting vertical line at x={args.xcolumn}") extract_column_strip_video(video_path, args.xcolumn, output_path, args.threshold, args.relax, - args.start, args.end, args.fps) + args.start, args.end, args.fps, args.timestamp) else: print(f"Row mode: Extracting horizontal line at y={args.yrow}") extract_row_strip_video(video_path, args.yrow, output_path, args.threshold, args.relax, - args.start, args.end, args.fps) + args.start, args.end, args.fps, args.timestamp) print("MJPEG video generation completed successfully!") else: diff --git a/readme.md b/readme.md index 32fd120..67f39fa 100644 --- a/readme.md +++ b/readme.md @@ -36,6 +36,11 @@ uv run main.py .\line500fps32pix.mp4 --video --fps 30 ``` Output: `results/video/line500fps32pix_a3f2_t0_01_fps30_0.avi` +**Video with Timestamp** - Add frame count overlay to video: +```bash +uv run main.py .\line500fps32pix.mp4 --video --fps 30 --timestamp +``` + **Debug Mode** - Analyze changes and generate threshold recommendations: ```bash uv run main.py .\line500fps32pix.mp4 --debug @@ -68,6 +73,7 @@ uv sync - `--debug` - Analyze changes without creating strip image, outputs to `results/debug/` - `--video` - Generate MJPEG video showing accumulated scan lines over time - `--fps N` - Output video frame rate (default: 30.0, only used with `--video`) +- `--timestamp` / `--ts` - Embed frame count on bottom left corner (video mode only) - `--timeline` - Overlay frame numbers as timeline/ruler on output image (image mode only) - `--start N` - Start frame number (0-based, default: 0) - `--end N` - End frame number (0-based, default: last frame) @@ -112,6 +118,7 @@ uv sync - Creates MJPEG AVI files showing scan line accumulation over time - Each video frame shows the progressive build-up of the strip photography effect - Configurable frame rate with `--fps` parameter +- Optional timestamp overlay with `--timestamp` / `--ts` showing frame count in bottom left corner - Video dimensions automatically calculated based on input video and scan line count - Compatible with both row and column extraction modes - Timeline overlay not supported in video mode (use image mode with `--timeline` instead)