|
2 | 2 | # |
3 | 3 | # This procedure extracts frames from a video and uses the agent's visual judgment |
4 | 4 | # to select the most flattering frame through a tournament-style comparison. |
| 5 | +# |
| 6 | +# ADAPTIVE BEHAVIOR: |
| 7 | +# - Round 1: Dynamically adjusts FPS based on video duration (targets 20-50 frames) |
| 8 | +# - FPS bounded between 0.1-2.0 fps to avoid extremes |
| 9 | +# - Minimum 10 frames guaranteed even for very short videos |
| 10 | +# - Round 2: Adapts window size based on video length |
| 11 | +# - Videos <10s: ±0.5s window at 20 fps for tight precision |
| 12 | +# - Videos ≥10s: ±1.0s window at 10 fps for standard refinement |
5 | 13 |
|
6 | 14 | ## Invocation |
7 | 15 | - Primary command: "extract-best-frame <video_path> [<frames_dir>] [<output_dir>]" |
|
13 | 21 | Ensure the video file exists: |
14 | 22 | !test -f "$VIDEO_PATH" || { echo "Error: Video file not found: $VIDEO_PATH"; exit 1; } |
15 | 23 |
|
16 | | -## Step 2: Extract Frames |
| 24 | +## Step 2: Extract Frames (Adaptive) |
| 25 | + |
| 26 | +Get video duration to calculate optimal frame extraction rate: |
| 27 | +!DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$VIDEO_PATH") |
| 28 | +!echo "Video duration: ${DURATION}s" |
| 29 | + |
| 30 | +Calculate adaptive FPS targeting 20-50 frames for Round 1: |
| 31 | +!TARGET_FRAMES=30 |
| 32 | +!FPS=$(echo "scale=3; $TARGET_FRAMES / $DURATION" | bc) |
17 | 33 |
|
18 | | -Extract frames at regular intervals (every 2 seconds): |
19 | | -!ffmpeg -i "$VIDEO_PATH" -vf "fps=0.5" -q:v 2 "$FRAMES_DIR/frame_%04d.jpg" -loglevel error |
| 34 | +Cap FPS between reasonable bounds (0.1 to 2.0 fps): |
| 35 | +!if (( $(echo "$FPS > 2.0" | bc -l) )); then FPS=2.0; fi |
| 36 | +!if (( $(echo "$FPS < 0.1" | bc -l) )); then FPS=0.1; fi |
| 37 | +!FRAME_INTERVAL=$(echo "scale=2; 1 / $FPS" | bc) |
| 38 | +!echo "Using adaptive FPS: $FPS (1 frame every ${FRAME_INTERVAL}s)" |
| 39 | + |
| 40 | +Extract frames at adaptive intervals: |
| 41 | +!ffmpeg -i "$VIDEO_PATH" -vf "fps=$FPS" -q:v 2 "$FRAMES_DIR/frame_%04d.jpg" -loglevel error |
20 | 42 |
|
21 | 43 | Count the extracted frames: |
22 | 44 | !FRAME_COUNT=$(ls -1 "$FRAMES_DIR"/frame_*.jpg 2>/dev/null | wc -l) |
23 | 45 | !echo "Extracted $FRAME_COUNT frames from video" |
24 | 46 |
|
| 47 | +Ensure minimum frame coverage for very short videos: |
| 48 | +!MIN_FRAMES=10 |
| 49 | +!if [ "$FRAME_COUNT" -lt "$MIN_FRAMES" ]; then |
| 50 | +! echo "Warning: Only $FRAME_COUNT frames extracted. Re-extracting with higher FPS for better coverage..." |
| 51 | +! ffmpeg -i "$VIDEO_PATH" -vf "fps=2.0" -q:v 2 "$FRAMES_DIR/frame_%04d.jpg" -loglevel error -y |
| 52 | +! FRAME_COUNT=$(ls -1 "$FRAMES_DIR"/frame_*.jpg 2>/dev/null | wc -l) |
| 53 | +! echo "Re-extracted $FRAME_COUNT frames for analysis" |
| 54 | +!fi |
| 55 | + |
25 | 56 | ## Step 3: Tournament Selection Using Claude |
26 | 57 |
|
27 | 58 | Now I'll help you find the best selfie frame using a tournament-style selection process. |
@@ -54,19 +85,36 @@ The selection process: |
54 | 85 | 3. Continue until one frame remains |
55 | 86 | 4. That frame is saved as the best selfie |
56 | 87 |
|
57 | | -## Step 4b: Round 2 - Fine-Grained Selection |
| 88 | +## Step 4b: Round 2 - Fine-Grained Selection (Adaptive) |
58 | 89 |
|
59 | 90 | After identifying the best frame from Round 1, perform fine-grained refinement: |
60 | 91 |
|
61 | 92 | Calculate the timestamp of the Round 1 winner and extract refined frames: |
62 | 93 | !WINNER_NUMBER=$(echo "$BEST_FRAME" | grep -o '[0-9]\+') |
63 | | -!WINNER_TIME=$((WINNER_NUMBER * 2)) # Since we extracted at 0.5 fps (every 2 seconds) |
64 | | -!START_TIME=$((WINNER_TIME - 1)) |
| 94 | +!WINNER_TIME=$(echo "scale=2; $WINNER_NUMBER * $FRAME_INTERVAL" | bc) |
| 95 | +!echo "Round 1 winner is at approximately ${WINNER_TIME}s in the video" |
| 96 | + |
| 97 | +Determine adaptive Round 2 parameters based on video duration: |
| 98 | +!if (( $(echo "$DURATION < 10" | bc -l) )); then |
| 99 | +! # Short videos: tighter window, higher precision |
| 100 | +! WINDOW=0.5 |
| 101 | +! ROUND2_FPS=20 |
| 102 | +! echo "Short video detected: Using ±${WINDOW}s window with ${ROUND2_FPS} fps" |
| 103 | +!else |
| 104 | +! # Longer videos: standard window |
| 105 | +! WINDOW=1.0 |
| 106 | +! ROUND2_FPS=10 |
| 107 | +! echo "Using standard ±${WINDOW}s window with ${ROUND2_FPS} fps" |
| 108 | +!fi |
| 109 | + |
| 110 | +Calculate Round 2 extraction window: |
| 111 | +!START_TIME=$(echo "scale=2; if ($WINNER_TIME - $WINDOW < 0) 0 else $WINNER_TIME - $WINDOW" | bc) |
| 112 | +!DURATION_R2=$(echo "scale=2; $WINDOW * 2" | bc) |
65 | 113 | !mkdir -p "$FRAMES_DIR/round2" |
66 | 114 |
|
67 | | -Extract 20 frames at 0.1-second intervals around the winner (±1 second window): |
68 | | -!ffmpeg -ss $START_TIME -i "$VIDEO_PATH" -t 2 -vf "fps=10" -q:v 2 "$FRAMES_DIR/round2/refined_%03d.jpg" -loglevel error |
69 | | -!echo "Extracted $(ls -1 "$FRAMES_DIR"/round2/*.jpg | wc -l) refined frames for Round 2" |
| 115 | +Extract refined frames around the winner: |
| 116 | +!ffmpeg -ss $START_TIME -i "$VIDEO_PATH" -t $DURATION_R2 -vf "fps=$ROUND2_FPS" -q:v 2 "$FRAMES_DIR/round2/refined_%03d.jpg" -loglevel error |
| 117 | +!echo "Extracted $(ls -1 "$FRAMES_DIR"/round2/*.jpg 2>/dev/null | wc -l) refined frames for Round 2" |
70 | 118 |
|
71 | 119 | [Claude will compare the refined frames to find the absolute best moment, capturing micro-expressions and perfect timing] |
72 | 120 |
|
|
0 commit comments