package cmd
import (
"encoding/json"
"flag"
"fmt"
"os"
"skraak/tools"
)
func printClipUsage() {
fmt.Fprintf(os.Stderr, "Usage: skraak calls clip [options]\n\n")
fmt.Fprintf(os.Stderr, "Generate audio clips and spectrogram images from .data file segments.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
fmt.Fprintf(os.Stderr, " --file <path> Path to .data file (required if no --folder)\n")
fmt.Fprintf(os.Stderr, " --folder <path> Path to folder containing .data files (required if no --file)\n")
fmt.Fprintf(os.Stderr, " --output <path> Output folder for generated clips (required)\n")
fmt.Fprintf(os.Stderr, " --prefix <name> Prefix for output filenames (required)\n")
fmt.Fprintf(os.Stderr, " --filter <name> Filter by ML model name (optional)\n")
fmt.Fprintf(os.Stderr, " --species <name> Filter by species, optionally with calltype (e.g. Kiwi, Kiwi+Duet)\n")
fmt.Fprintf(os.Stderr, " --certainty <int> Filter by certainty value (0-100, optional)\n")
fmt.Fprintf(os.Stderr, " --size <int> Spectrogram image size in pixels (224-896, default 224)\n")
fmt.Fprintf(os.Stderr, " --color Apply L4 colormap to spectrogram (default: grayscale)\n")
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")
fmt.Fprintf(os.Stderr, "\nOutput files:\n")
fmt.Fprintf(os.Stderr, " <prefix>_<basename>_<start>_<end>.png # spectrogram image\n")
fmt.Fprintf(os.Stderr, " <prefix>_<basename>_<start>_<end>.wav # audio clip (16kHz if downsampled)\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " # Clip all segments from a single file\n")
fmt.Fprintf(os.Stderr, " skraak calls clip --file recording.data --output ./clips --prefix train\n\n")
fmt.Fprintf(os.Stderr, " # Clip only Kiwi segments with color spectrograms at 448px\n")
fmt.Fprintf(os.Stderr, " skraak calls clip --folder ./data --output ./clips --prefix kiwi \\\n")
fmt.Fprintf(os.Stderr, " --filter opensoundscape-kiwi-1.2 --species Kiwi --size 448 --color\n\n")
fmt.Fprintf(os.Stderr, " # Clip Kiwi Duet calls\n")
fmt.Fprintf(os.Stderr, " skraak calls clip --folder ./data --output ./clips --prefix duet \\\n")
fmt.Fprintf(os.Stderr, " --filter opensoundscape-kiwi-1.2 --species Kiwi+Duet\n")
}
type clipFlags struct {
file string
folder string
output string
prefix string
filter string
species string
size int
certainty int
color bool
night bool
day bool
location string
}
func parseClipArgs(args []string) clipFlags {
fs := flag.NewFlagSet("calls clip", flag.ExitOnError)
file := fs.String("file", "", "Path to .data file (required if no --folder)")
folder := fs.String("folder", "", "Path to folder containing .data files (required if no --file)")
output := fs.String("output", "", "Output folder for generated clips (required)")
prefix := fs.String("prefix", "", "Prefix for output filenames (required)")
filter := fs.String("filter", "", "Filter by ML model name")
species := fs.String("species", "", "Filter by species, optionally with calltype")
certainty := fs.Int("certainty", -1, "Filter by certainty value (0-100)")
size := fs.Int("size", 0, "Spectrogram image size in pixels (224-896)")
color := fs.Bool("color", false, "Apply L4 colormap to spectrogram")
night := fs.Bool("night", false, "Only clip recordings made during solar night")
day := fs.Bool("day", false, "Only clip recordings made during solar day")
location := fs.String("location", "", "GPS coordinates and optional timezone")
fs.Usage = printClipUsage
_ = fs.Parse(args)
if *certainty < -1 || *certainty > 100 {
fmt.Fprintf(os.Stderr, "Error: --certainty must be between 0 and 100\n")
os.Exit(1)
}
return clipFlags{
file: *file,
folder: *folder,
output: *output,
prefix: *prefix,
filter: *filter,
species: *species,
size: *size,
certainty: *certainty,
color: *color,
night: *night,
day: *day,
location: *location,
}
}
func validateClipFlags(f clipFlags) {
missing := []string{}
if f.file == "" && f.folder == "" {
missing = append(missing, "--file or --folder")
}
if f.output == "" {
missing = append(missing, "--output")
}
if f.prefix == "" {
missing = append(missing, "--prefix")
}
if len(missing) > 0 {
fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
printClipUsage()
os.Exit(1)
}
if f.night && f.day {
fmt.Fprintf(os.Stderr, "Error: --night and --day are mutually exclusive\n\n")
printClipUsage()
os.Exit(1)
}
if (f.night || f.day) && f.location == "" {
fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
printClipUsage()
os.Exit(1)
}
}
func RunCallsClip(args []string) {
f := parseClipArgs(args)
validateClipFlags(f)
input := tools.CallsClipInput{
File: f.file,
Folder: f.folder,
Output: f.output,
Prefix: f.prefix,
Filter: f.filter,
Species: f.species,
Certainty: f.certainty,
Size: f.size,
Color: f.color,
Night: f.night,
Day: f.day,
Location: f.location,
}
result, err := tools.CallsClip(input)
if err != nil {
data, _ := json.Marshal(result)
fmt.Println(string(data))
os.Exit(1)
}
data, _ := json.Marshal(result)
fmt.Println(string(data))
}