tidy up lat lng timezone api for calls clip cmd

quietlight
May 4, 2026, 8:06 PM
YE6BZJUKQ7VMYEKKI3WSKTZEBR5NWUUDIN6PGE4W7OTPIY5N3NJQC

Dependencies

Change contents

  • edit in tools/calls_clip.go at line 10
    [4.574177]
    [4.574177]
    "strconv"
  • replacement in tools/calls_clip.go at line 29
    [3.393][3.393:555]()
    Night bool `json:"night"`
    Day bool `json:"day"`
    Lat float64 `json:"lat"`
    Lng float64 `json:"lng"`
    Timezone string `json:"timezone"`
    [3.393]
    [4.574817]
    Night bool `json:"night"`
    Day bool `json:"day"`
    Location string `json:"location,omitempty"`
  • edit in tools/calls_clip.go at line 71
    [4.576828]
    [4.576828]
    // Parse location into lat/lng/timezone
    var lat, lng float64
    var timezone string
    if input.Location != "" {
    var err error
    lat, lng, timezone, err = parseLocation(input.Location)
    if err != nil {
    output.Errors = append(output.Errors, err.Error())
    return output, err
    }
    }
  • replacement in tools/calls_clip.go at line 85
    [4.576908][4.7806:7890]()
    processFilesSequential(&output, filePaths, input, speciesName, callType, imgSize)
    [4.576908]
    [4.577545]
    processFilesSequential(&output, filePaths, input, speciesName, callType, imgSize, lat, lng, timezone)
  • replacement in tools/calls_clip.go at line 87
    [4.577555][4.7891:7973]()
    processFilesParallel(&output, filePaths, input, speciesName, callType, imgSize)
    [4.577555]
    [4.7973]
    processFilesParallel(&output, filePaths, input, speciesName, callType, imgSize, lat, lng, timezone)
  • edit in tools/calls_clip.go at line 125
    [4.9215]
    [4.9215]
    }
    // parseLocation parses a "lat,lng[,timezone]" string into its components.
    // Timezone is optional and defaults to "" (UTC).
    func parseLocation(location string) (lat, lng float64, timezone string, err error) {
    parts := strings.Split(location, ",")
    if len(parts) < 2 || len(parts) > 3 {
    return 0, 0, "", fmt.Errorf("--location must be \"lat,lng\" or \"lat,lng,timezone\" (got %d parts)", len(parts))
    }
    lat, err = strconv.ParseFloat(strings.TrimSpace(parts[0]), 64)
    if err != nil {
    return 0, 0, "", fmt.Errorf("--location: invalid latitude: %s", parts[0])
    }
    lng, err = strconv.ParseFloat(strings.TrimSpace(parts[1]), 64)
    if err != nil {
    return 0, 0, "", fmt.Errorf("--location: invalid longitude: %s", parts[1])
    }
    if len(parts) == 3 {
    timezone = strings.TrimSpace(parts[2])
    }
    return lat, lng, timezone, nil
  • replacement in tools/calls_clip.go at line 149
    [4.9281][4.9281:9421]()
    func processFilesSequential(output *CallsClipOutput, filePaths []string, input CallsClipInput, speciesName, callType string, imgSize int) {
    [4.9281]
    [4.9421]
    func processFilesSequential(output *CallsClipOutput, filePaths []string, input CallsClipInput, speciesName, callType string, imgSize int, lat, lng float64, timezone string) {
  • replacement in tools/calls_clip.go at line 151
    [4.9459][3.556:770]()
    clips, skipped, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, input.Certainty, imgSize, input.Color, input.Night, input.Day, input.Lat, input.Lng, input.Timezone)
    [4.9459]
    [4.9688]
    clips, skipped, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, input.Certainty, imgSize, input.Color, input.Night, input.Day, lat, lng, timezone)
  • replacement in tools/calls_clip.go at line 157
    [4.9831][4.9831:9969]()
    func processFilesParallel(output *CallsClipOutput, filePaths []string, input CallsClipInput, speciesName, callType string, imgSize int) {
    [4.9831]
    [4.9969]
    func processFilesParallel(output *CallsClipOutput, filePaths []string, input CallsClipInput, speciesName, callType string, imgSize int, lat, lng float64, timezone string) {
  • replacement in tools/calls_clip.go at line 172
    [4.10291][3.771:987]()
    clips, skipped, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, input.Certainty, imgSize, input.Color, input.Night, input.Day, input.Lat, input.Lng, input.Timezone)
    [4.10291]
    [4.10522]
    clips, skipped, errs := processFile(dataPath, input.Output, input.Prefix, input.Filter, speciesName, callType, input.Certainty, imgSize, input.Color, input.Night, input.Day, lat, lng, timezone)
  • edit in cmd/calls_clip.go at line 41
    [4.1133178][4.15765:16065](),[4.16065][4.26764:26767]()
    // nextFloat parses the next argument as a float64, or exits with an error.
    func (p *clipArgParser) nextFloat(flag string) float64 {
    s := p.nextValue(flag)
    v, err := strconv.ParseFloat(s, 64)
    if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %s must be a number\n", flag)
    os.Exit(1)
    }
    return v
    }
  • replacement in cmd/calls_clip.go at line 63
    [4.1134411][4.1134411:1135310]()
    fmt.Fprintf(os.Stderr, " --night Only clip recordings made during solar night (requires --lat and --lng)\n")
    fmt.Fprintf(os.Stderr, " --day Only clip recordings made during solar day (requires --lat and --lng)\n")
    fmt.Fprintf(os.Stderr, " --lat <float> Latitude in decimal degrees (required with --night or --day)\n")
    fmt.Fprintf(os.Stderr, " --lng <float> Longitude in decimal degrees (required with --night or --day)\n")
    fmt.Fprintf(os.Stderr, " --timezone <zone> IANA timezone ID (e.g. Pacific/Auckland). Required for non-AudioMoth\n")
    fmt.Fprintf(os.Stderr, " recorders whose filenames embed local time (e.g. DOC AR4).\n")
    fmt.Fprintf(os.Stderr, " AudioMoth files embed a UTC timestamp in the WAV comment, so\n")
    fmt.Fprintf(os.Stderr, " --timezone is not needed for AudioMoth data.\n")
    [4.1134304]
    [4.1135310]
    fmt.Fprintf(os.Stderr, " --night Only clip recordings made during solar night (requires --location)\n")
    fmt.Fprintf(os.Stderr, " --day Only clip recordings made during solar day (requires --location)\n")
    fmt.Fprintf(os.Stderr, " --location <lat,lng[,tz]> GPS coordinates and optional IANA timezone\n")
    fmt.Fprintf(os.Stderr, " e.g. --location \"-36.85,174.76\" or --location \"-36.85,174.76,Pacific/Auckland\"\n")
    fmt.Fprintf(os.Stderr, " Required with --night or --day. Timezone defaults to UTC.\n")
    fmt.Fprintf(os.Stderr, " Not needed for AudioMoth data (UTC from WAV comment).\n")
  • edit in cmd/calls_clip.go at line 91
    [4.27247][4.27247:27265]()
    timezone string
  • replacement in cmd/calls_clip.go at line 96
    [4.27359][4.27359:27429]()
    lat float64
    lng float64
    latSet bool
    lngSet bool
    [4.27359]
    [4.27429]
    location string
  • replacement in cmd/calls_clip.go at line 136
    [4.16413][4.1139273:1139289](),[4.1139273][4.1139273:1139289](),[4.1139289][4.27912:27959](),[4.27959][4.1139583:1139599](),[4.1139583][4.1139583:1139599](),[4.1139599][4.27960:28007](),[4.28007][4.1139893:1139914](),[4.1139893][4.1139893:1139914](),[4.1139914][4.28008:28041]()
    case "--lat":
    f.lat = p.nextFloat(arg)
    f.latSet = true
    case "--lng":
    f.lng = p.nextFloat(arg)
    f.lngSet = true
    case "--timezone":
    f.timezone = p.nextValue(arg)
    [4.16413]
    [4.1140061]
    case "--location":
    f.location = p.nextUniqueValue(arg, f.location)
  • replacement in cmd/calls_clip.go at line 175
    [4.1140845][4.28265:28318](),[4.28318][4.1140890:1140973](),[4.1140890][4.1140890:1140973]()
    if (f.night || f.day) && (!f.latSet || !f.lngSet) {
    fmt.Fprintf(os.Stderr, "Error: --night/--day requires both --lat and --lng\n\n")
    [4.1140845]
    [4.1140973]
    if (f.night || f.day) && f.location == "" {
    fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
  • replacement in cmd/calls_clip.go at line 211
    [4.29205][4.29205:29270]()
    Lat: f.lat,
    Lng: f.lng,
    Timezone: f.timezone,
    [4.29205]
    [4.1141364]
    Location: f.location,
  • edit in CHANGELOG.md at line 4
    [4.1198010]
    [2.27627]
    ## [2026-05-05] `calls clip`: replace --lat/--lng/--timezone with --location; remove --wav-only
    **Breaking CLI change.** Two flag changes to `skraak calls clip`:
    1. **`--lat`, `--lng`, `--timezone` → `--location`** — The three GPS/timezone
    flags are replaced by a single `--location "lat,lng[,timezone]"` flag.
    Timezone is optional (defaults to UTC; not needed for AudioMoth).
    This makes invalid states unrepresentable (you can't pass lat without lng).
    Before:
    ```bash
    skraak calls clip --folder ./data --output ./clips --prefix kiwi \
    --species Kiwi --night --lat -40.85 --lng 172.81 --timezone Pacific/Auckland
    ```
    After:
    ```bash
    skraak calls clip --folder ./data --output ./clips --prefix kiwi \
    --species Kiwi --night --location "-40.85,172.81,Pacific/Auckland"
    ```
  • edit in CHANGELOG.md at line 25
    [2.27628]
    [2.27628]
    2. **`--wav-only` removed** — This flag skipped spectrogram PNG generation.
    Removed as unnecessary; the tool's primary purpose is generating training
    data (PNG+WAV pairs).
    **Files changed:**
    - `cmd/calls_clip.go` — flag parsing, validation, usage text
    - `tools/calls_clip.go` — `CallsClipInput` struct, `parseLocation` helper,
    removed `wavOnly` from function signatures, unwrapped spectrogram conditional
    - `tools/calls_clip_bench_test.go` — removed `BenchmarkFullPipelineWavOnly`