Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

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, ", ")
}