Skip to content

Commit c80dd84

Browse files
Brandon Salzbergclaude
andcommitted
Add --activity type filter to stitch command
Allow filtering seekpoints by activity type (Human, Vehicle, Animal, etc.) with user-friendly aliases that map to ActivityEnum values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 311b128 commit c80dd84

File tree

3 files changed

+52
-5
lines changed

3 files changed

+52
-5
lines changed

cmd/analyze.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func runAnalyzeFootage(cmd *cobra.Command, args []string) error {
321321
os.MkdirAll(camDir, 0755)
322322

323323
// Get seekpoints for activity-aware frame selection
324-
activityTimes := getActivityTimes(cfg, camUUID, startMs, endMs, includeMotion)
324+
activityTimes := getActivityTimes(cfg, camUUID, startMs, endMs, includeMotion, nil)
325325

326326
// Select frames: prioritize activity, optionally fill remainder evenly
327327
frameTimes := selectFrameTimes(startMs, endMs, interval, activityTimes, fill)
@@ -380,7 +380,35 @@ func runAnalyzeFootage(cmd *cobra.Command, args []string) error {
380380

381381
// ─── Frame Selection ────────────────────────────────────────────────
382382

383-
func getActivityTimes(cfg config.Config, cameraUUID string, startMs, endMs int64, includeMotion bool) []int64 {
383+
// activityAliases maps user-friendly names to ActivityEnum values.
384+
var activityAliases = map[string]string{
385+
"human": "MOTION_HUMAN",
386+
"vehicle": "MOTION_CAR",
387+
"car": "MOTION_CAR",
388+
"animal": "MOTION_ANIMAL",
389+
"face": "FACE",
390+
"licenseplate": "LICENSEPLATE",
391+
"motion": "MOTION",
392+
"tamper": "TAMPER",
393+
"sound": "SOUND_LOUD",
394+
"gunshot": "SOUND_GUN_SHOT",
395+
}
396+
397+
// resolveActivityTypes converts user-friendly names to ActivityEnum values.
398+
// Unrecognized values are passed through as-is (assumed to be raw enum values).
399+
func resolveActivityTypes(types []string) []string {
400+
var resolved []string
401+
for _, t := range types {
402+
if mapped, ok := activityAliases[strings.ToLower(strings.TrimSpace(t))]; ok {
403+
resolved = append(resolved, mapped)
404+
} else {
405+
resolved = append(resolved, strings.TrimSpace(t))
406+
}
407+
}
408+
return resolved
409+
}
410+
411+
func getActivityTimes(cfg config.Config, cameraUUID string, startMs, endMs int64, includeMotion bool, activityTypes []string) []int64 {
384412
startSec := startMs / 1000
385413
durationSec := (endMs - startMs) / 1000
386414

@@ -389,7 +417,7 @@ func getActivityTimes(cfg config.Config, cameraUUID string, startMs, endMs int64
389417
"startTime": startSec,
390418
"duration": durationSec,
391419
}
392-
if includeMotion {
420+
if includeMotion || len(activityTypes) > 0 {
393421
params["includeAnyMotion"] = "true"
394422
}
395423

@@ -401,12 +429,26 @@ func getActivityTimes(cfg config.Config, cameraUUID string, startMs, endMs int64
401429

402430
seekpoints, _ := resp["footageSeekPoints"].([]any)
403431
fmt.Fprintf(os.Stderr, " Seekpoints found: %d\n", len(seekpoints))
432+
433+
// Build filter set
434+
filterSet := make(map[string]bool, len(activityTypes))
435+
for _, at := range activityTypes {
436+
filterSet[at] = true
437+
}
438+
404439
var times []int64
405440
for _, sp := range seekpoints {
406441
s, ok := sp.(map[string]any)
407442
if !ok {
408443
continue
409444
}
445+
// Filter by activity type if specified
446+
if len(filterSet) > 0 {
447+
activity, _ := s["a"].(string)
448+
if !filterSet[activity] {
449+
continue
450+
}
451+
}
410452
ts, _ := s["ts"].(float64)
411453
if ts > 0 {
412454
times = append(times, int64(ts))

cmd/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ func runContextCamera(cmd *cobra.Command, args []string) error {
467467

468468
// Recent activity summary
469469
nowMs := time.Now().UnixMilli()
470-
activity := getActivityTimes(cfg, camUUID, nowMs-3600*1000, nowMs, false)
470+
activity := getActivityTimes(cfg, camUUID, nowMs-3600*1000, nowMs, false, nil)
471471
fmt.Printf("\nActivity (last hour): %d events\n", len(activity))
472472

473473
return nil

cmd/stitch.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ in a grid layout. Timestamps are overlaid on each clip.`,
5151
stitchCmd.Flags().String("period", "", "Natural language time window (e.g., 'yesterday between 6am and 7am')")
5252
stitchCmd.Flags().Int("buffer", 5, "Seconds of buffer around each event")
5353
stitchCmd.Flags().Bool("include-motion", false, "Include motion seekpoints (default: only human/vehicle/object activity)")
54+
stitchCmd.Flags().StringSlice("activity", nil, "Filter by activity type (Human, Vehicle, Animal, Face, LicensePlate, Motion, or raw enum)")
5455
stitchCmd.Flags().String("output", "", "Output file path (default: auto-generated)")
5556
rootCmd.AddCommand(stitchCmd)
5657
}
@@ -64,8 +65,12 @@ func runStitch(cmd *cobra.Command, args []string) error {
6465
periodStr, _ := cmd.Flags().GetString("period")
6566
buffer, _ := cmd.Flags().GetInt("buffer")
6667
includeMotion, _ := cmd.Flags().GetBool("include-motion")
68+
activityFilter, _ := cmd.Flags().GetStringSlice("activity")
6769
outputPath, _ := cmd.Flags().GetString("output")
6870

71+
// Resolve user-friendly activity type names to enum values
72+
activityTypes := resolveActivityTypes(activityFilter)
73+
6974
// Check ffmpeg
7075
if _, err := exec.LookPath("ffmpeg"); err != nil {
7176
return fmt.Errorf("ffmpeg is required. Install with: brew install ffmpeg")
@@ -178,7 +183,7 @@ func runStitch(cmd *cobra.Command, args []string) error {
178183
camName = camUUID
179184
}
180185

181-
activityTimes := getActivityTimes(cfg, camUUID, startMs, endMs, includeMotion)
186+
activityTimes := getActivityTimes(cfg, camUUID, startMs, endMs, includeMotion, activityTypes)
182187
if len(activityTimes) == 0 {
183188
continue
184189
}

0 commit comments

Comments
 (0)