JR46EPFZI2AVLOZH2OFQIZZYBTEWJ3YTZAFVZQGYFLRMBU6ZEPYQC // sunTimeUTC returns the UTC RFC3339 string for a suncalc event, or "" if absent/zero.func sunTimeUTC(sunTimes map[suncalc.DayTimeName]suncalc.DayTime, name suncalc.DayTimeName) string {if entry, ok := sunTimes[name]; ok && !entry.Value.IsZero() {return entry.Value.UTC().Format(time.RFC3339)}return ""}
}if sr, ok := sunTimes[suncalc.Sunrise]; ok && !sr.Value.IsZero() {output.SunriseUTC = sr.Value.UTC().Format(time.RFC3339)}if ss, ok := sunTimes[suncalc.Sunset]; ok && !ss.Value.IsZero() {output.SunsetUTC = ss.Value.UTC().Format(time.RFC3339)
if d, ok := sunTimes[suncalc.Dawn]; ok && !d.Value.IsZero() {output.DawnUTC = d.Value.UTC().Format(time.RFC3339)}if dk, ok := sunTimes[suncalc.Dusk]; ok && !dk.Value.IsZero() {output.DuskUTC = dk.Value.UTC().Format(time.RFC3339)}
output.SunriseUTC = sunTimeUTC(sunTimes, suncalc.Sunrise)output.SunsetUTC = sunTimeUTC(sunTimes, suncalc.Sunset)output.DawnUTC = sunTimeUTC(sunTimes, suncalc.Dawn)output.DuskUTC = sunTimeUTC(sunTimes, suncalc.Dusk)
hasFilter := config.Filter != "" || config.Species != "" || config.Certainty >= 0var segs []*utils.Segmentif !hasFilter {segs = df.Segments} else {for _, seg := range df.Segments {if seg.SegmentMatchesFilters(config.Filter, config.Species, config.CallType, config.Certainty) {segs = append(segs, seg)}}if len(segs) == 0 {return nil, false, 0}
segs := filterSegmentsByLabel(df.Segments, config)if segs == nil {return nil, false, 0
wavPath := filepath.Clean(strings.TrimSuffix(df.FilePath, ".data"))result, err := IsNight(IsNightInput{FilePath: wavPath,Lat: config.Lat,Lng: config.Lng,Timezone: config.Timezone,})if err != nil {fmt.Fprintf(os.Stderr, "warning: skipping %s (isnight error: %v)\n", wavPath, err)return nil, false, 1
keep, tf := filterByTimeOfDay(df.FilePath, config)if !keep {return nil, false, tf
}// filterSegmentsByLabel applies label/species/certainty filters, returning matching segments.// Returns nil if no segments match (caller should skip the file).func filterSegmentsByLabel(segments []*utils.Segment, config ClassifyConfig) []*utils.Segment {hasFilter := config.Filter != "" || config.Species != "" || config.Certainty >= 0if !hasFilter {return segments}var segs []*utils.Segmentfor _, seg := range segments {if seg.SegmentMatchesFilters(config.Filter, config.Species, config.CallType, config.Certainty) {segs = append(segs, seg)}}return segs // nil if empty, caller treats as "skip"
// filterByTimeOfDay checks --night/--day time-of-day filter for a .data file.// Returns (keep, timeFilteredCount).func filterByTimeOfDay(dataFilePath string, config ClassifyConfig) (bool, int) {wavPath := filepath.Clean(strings.TrimSuffix(dataFilePath, ".data"))result, err := IsNight(IsNightInput{FilePath: wavPath,Lat: config.Lat,Lng: config.Lng,Timezone: config.Timezone,})if err != nil {fmt.Fprintf(os.Stderr, "warning: skipping %s (isnight error: %v)\n", wavPath, err)return false, 1}if config.Night && !result.SolarNight {return false, 1}if config.Day && !result.DiurnalActive {return false, 1}return true, 0}