package cmd
import (
"encoding/json"
"flag"
"fmt"
"os"
"sort"
"skraak/tools"
)
func runCallsClipLabels(args []string) {
fs := flag.NewFlagSet("calls clip-labels", flag.ExitOnError)
folder := fs.String("folder", "", "Folder containing .data files (required)")
mapping := fs.String("mapping", "", "Path to mapping.json (required)")
filter := fs.String("filter", "", "Restrict to a single filter name (default: all filters)")
output := fs.String("output", "./clip_labels.csv", "Output CSV path")
clipDuration := fs.Float64("clip-duration", 4.0, "Clip duration in seconds")
clipOverlap := fs.Float64("clip-overlap", 0.5, "Clip overlap in seconds")
minLabelOverlap := fs.Float64("min-label-overlap", 0.25, "Minimum overlap (s) for an annotation to label a clip")
finalClip := fs.String("final-clip", "full", "Trailing-clip behaviour: full | remainder | extend | none")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: skraak calls clip-labels [options]\n\n")
fmt.Fprintf(os.Stderr, "Generate an OpenSoundScape clip_labels-format CSV from .data files.\n\n")
fmt.Fprintf(os.Stderr, "Options:\n")
fs.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nSegment policy:\n")
fmt.Fprintf(os.Stderr, " - Real species → contributes mapped class to overlapping clips.\n")
fmt.Fprintf(os.Stderr, " - Mapped to __NEGATIVE__ → clip emitted, all class columns False;\n")
fmt.Fprintf(os.Stderr, " overrides positives in the same clip.\n")
fmt.Fprintf(os.Stderr, " - Mapped to __IGNORE__ → segment contributes no labels to clips.\n")
fmt.Fprintf(os.Stderr, " - Gaps → clip emitted with all class columns False.\n")
fmt.Fprintf(os.Stderr, "\nIf --output exists: append. Column-set mismatch → hard error.\n")
fmt.Fprintf(os.Stderr, "Duplicate (file, start_time, end_time) row → hard error on first.\n")
fmt.Fprintf(os.Stderr, "\nExamples:\n")
fmt.Fprintf(os.Stderr, " skraak calls clip-labels --folder ./recordings --mapping ./mapping.json\n")
fmt.Fprintf(os.Stderr, " skraak calls clip-labels --folder ./recordings --mapping ./mapping.json \\\n")
fmt.Fprintf(os.Stderr, " --filter opensoundscape-multi-1.0\n")
}
if err := fs.Parse(args); err != nil {
os.Exit(1)
}
if *folder == "" {
fmt.Fprintf(os.Stderr, "Error: --folder is required\n\n")
fs.Usage()
os.Exit(1)
}
if *mapping == "" {
fmt.Fprintf(os.Stderr, "Error: --mapping is required\n\n")
fs.Usage()
os.Exit(1)
}
input := tools.CallsClipLabelsInput{
Folder: *folder,
MappingPath: *mapping,
Filter: *filter,
OutputPath: *output,
ClipDuration: *clipDuration,
ClipOverlap: *clipOverlap,
MinLabelOverlap: *minLabelOverlap,
FinalClip: *finalClip,
}
fmt.Fprintf(os.Stderr, "Folder: %s\n", *folder)
fmt.Fprintf(os.Stderr, "Mapping: %s\n", *mapping)
fmt.Fprintf(os.Stderr, "Output: %s\n", *output)
fmt.Fprintf(os.Stderr, "Clip: duration=%.3fs overlap=%.3fs final=%s min-label-overlap=%.3fs\n",
*clipDuration, *clipOverlap, *finalClip, *minLabelOverlap)
if *filter != "" {
fmt.Fprintf(os.Stderr, "Filter: %s\n", *filter)
}
out, err := tools.CallsClipLabels(input)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "\nResults\n")
fmt.Fprintf(os.Stderr, " .data files parsed: %d\n", out.DataFilesParsed)
fmt.Fprintf(os.Stderr, " Segments ignored (__IGNORE__): %d\n", out.SegmentsIgnored)
fmt.Fprintf(os.Stderr, " Clips excluded (__IGNORE__): %d\n", out.ClipsIgnored)
fmt.Fprintf(os.Stderr, " Clips emitted: %d\n", out.RowsWritten)
fmt.Fprintf(os.Stderr, " negative (__NEGATIVE__): %d\n", out.ClipsNegative)
fmt.Fprintf(os.Stderr, " all-False (gap): %d\n", out.ClipsAllFalseGap)
if out.AppendedToFile {
fmt.Fprintf(os.Stderr, " Appended to file: yes (%d existing rows)\n", out.ExistingRowsFound)
}
fmt.Fprintf(os.Stderr, "\nPer-class True counts:\n")
keys := make([]string, 0, len(out.PerClassTrueCount))
for k := range out.PerClassTrueCount {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(os.Stderr, " %-30s %d\n", k+":", out.PerClassTrueCount[k])
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(out); err != nil {
fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
os.Exit(1)
}
}