calls_show_images.go
package calls
import (
"fmt"
"os"
"strings"
"skraak/datafile"
"skraak/spectrogram"
)
// CallsShowImagesInput defines the input for the show-images tool
type CallsShowImagesInput struct {
DataFilePath string `json:"data_file_path"`
Color bool `json:"color"`
ImageSize int `json:"image_size"`
Sixel bool `json:"sixel"`
ITerm bool `json:"iterm"`
}
// CallsShowImagesOutput defines the output for the show-images tool
type CallsShowImagesOutput struct {
SegmentsShown int `json:"segments_shown"`
WavFile string `json:"wav_file"`
Error string `json:"error,omitempty"`
}
// CallsShowImages reads a .data file and displays spectrogram images for each segment
func CallsShowImages(input CallsShowImagesInput) (CallsShowImagesOutput, error) {
var output CallsShowImagesOutput
// Validate input and get data file
dataFile, wavPath, err := validateAndParseShowImagesInput(input)
if err != nil {
output.Error = err.Error()
return output, err
}
output.WavFile = wavPath
if len(dataFile.Segments) == 0 {
output.Error = "No segments found in .data file"
return output, fmt.Errorf("%s", output.Error)
}
// Resolve image size and protocol
imgSize := resolveImageSize(input.ImageSize)
protocol := resolveGraphicsProtocol(input)
// Generate and display spectrograms
segmentsShown, err := displaySegmentSpectrograms(input.DataFilePath, dataFile.Segments, input.Color, imgSize, protocol)
if err != nil {
output.Error = err.Error()
return output, err
}
output.SegmentsShown = segmentsShown
return output, nil
}
// validateAndParseShowImagesInput validates input files and parses the .data file.
func validateAndParseShowImagesInput(input CallsShowImagesInput) (*datafile.DataFile, string, error) {
if _, err := os.Stat(input.DataFilePath); os.IsNotExist(err) {
return nil, "", fmt.Errorf("file not found: %s", input.DataFilePath)
}
wavPath := strings.TrimSuffix(input.DataFilePath, ".data")
if _, err := os.Stat(wavPath); os.IsNotExist(err) {
return nil, "", fmt.Errorf("WAV file not found: %s", wavPath)
}
dataFile, err := datafile.ParseDataFile(input.DataFilePath)
if err != nil {
return nil, "", err
}
return dataFile, wavPath, nil
}
// resolveImageSize returns the image size, using default if zero.
func resolveImageSize(size int) int {
if size == 0 {
return spectrogram.SpectrogramDisplaySize
}
return size
}
// resolveGraphicsProtocol selects the terminal graphics protocol.
func resolveGraphicsProtocol(input CallsShowImagesInput) spectrogram.ImageProtocol {
if input.ITerm {
return spectrogram.ProtocolITerm
}
if input.Sixel {
return spectrogram.ProtocolSixel
}
return spectrogram.ProtocolKitty
}
// displaySegmentSpectrograms generates and displays spectrograms for each segment.
func displaySegmentSpectrograms(dataFilePath string, segments []*datafile.Segment, color bool, imgSize int, protocol spectrogram.ImageProtocol) (int, error) {
shown := 0
for i, seg := range segments {
img, err := spectrogram.GenerateSegmentSpectrogram(dataFilePath, seg.StartTime, seg.EndTime, color, imgSize)
if err != nil || img == nil {
continue
}
labelInfo := formatSegmentLabels(seg.Labels)
fmt.Fprintf(os.Stderr, "Segment %d: %.1fs - %.1fs (%.1fs)%s\n",
i+1, seg.StartTime, seg.EndTime, seg.EndTime-seg.StartTime, labelInfo)
if err := spectrogram.WriteImage(img, os.Stdout, protocol); err != nil {
return shown, fmt.Errorf("failed to write image: %w", err)
}
fmt.Println()
shown++
}
return shown, nil
}
// formatSegmentLabels formats labels for display in segment info
func formatSegmentLabels(labels []*datafile.Label) string {
if len(labels) == 0 {
return ""
}
var parts []string
for _, l := range labels {
part := l.Species
if l.CallType != "" {
part += "/" + l.CallType
}
if l.Filter != "" {
part += " [" + l.Filter + "]"
}
parts = append(parts, part)
}
return " " + strings.Join(parts, ", ")
}