audio file info added, fixed right click selecting.

This commit is contained in:
2026-02-23 19:05:57 -05:00
parent a6b476399e
commit 16060023bb
23 changed files with 798 additions and 134 deletions
+45 -11
View File
@@ -14,6 +14,16 @@ type ExtractionTiming struct {
DurationExtraction time.Duration
}
// AudioProperties holds technical properties of an audio file that
// are extracted from its stream headers during scanning.
type AudioProperties struct {
SampleRate int // Sample rate in Hz (e.g. 44100, 96000).
BitDepth int // Bits per sample (e.g. 16, 24).
Channels int // Number of audio channels (1=mono, 2=stereo).
Bitrate int // Bitrate in kbps.
FileSize int64 // File size in bytes.
}
// AudioFileExtension represents a supported audio file extension.
type AudioFileExtension string
@@ -60,25 +70,37 @@ func GetTrackLengthMillis(path string) (int64, error) {
return lengthMillis, nil
}
// ExtractAllMetadata opens the file once and extracts both tags and duration.
// This avoids the overhead of opening the file twice when both are needed.
// If skipDuration is true, only tags are extracted and lengthMillis is 0.
// ExtractAllMetadata opens the file once and extracts tags, duration,
// and audio properties (sample rate, bit depth, channels, bitrate,
// file size). If skipDuration is true, only tags are extracted and
// the remaining outputs are zero-valued.
// The returned ExtractionTiming records how long each sub-operation took.
func ExtractAllMetadata(
path string,
skipDuration bool,
) (*TrackMetadata, int64, *ExtractionTiming, error) {
) (*TrackMetadata, int64, *AudioProperties, *ExtractionTiming, error) {
timing := &ExtractionTiming{}
props := &AudioProperties{}
f, err := os.Open(path)
if err != nil {
return nil, 0, timing, fmt.Errorf(
return nil, 0, props, timing, fmt.Errorf(
"could not open file: %w", err,
)
}
defer func() { _ = f.Close() }()
// Capture file size.
fi, err := f.Stat()
if err != nil {
return nil, 0, props, timing, fmt.Errorf(
"could not stat file: %w", err,
)
}
props.FileSize = fi.Size()
// Extract tags first (reads only headers, fast).
tagStart := time.Now()
@@ -87,33 +109,45 @@ func ExtractAllMetadata(
timing.TagExtraction = time.Since(tagStart)
if err != nil {
return nil, 0, timing, fmt.Errorf(
return nil, 0, props, timing, fmt.Errorf(
"could not extract tags from %s: %w", path, err,
)
}
if skipDuration {
return tags, 0, timing, nil
return tags, 0, props, timing, nil
}
// Seek back to the beginning for duration extraction.
if _, err := f.Seek(0, io.SeekStart); err != nil {
return tags, 0, timing, fmt.Errorf(
return tags, 0, props, timing, fmt.Errorf(
"could not seek file for duration: %w", err,
)
}
durStart := time.Now()
lengthMillis, err := getTrackDuration(f)
lengthMillis, audioProps, err := getTrackDuration(f)
timing.DurationExtraction = time.Since(durStart)
if err != nil {
return tags, 0, timing, fmt.Errorf(
return tags, 0, props, timing, fmt.Errorf(
"error getting duration for %s: %w", path, err,
)
}
return tags, lengthMillis, timing, nil
// Merge stream properties into the result, keeping the
// file size we already captured.
audioProps.FileSize = props.FileSize
// Compute bitrate from file size and duration when the
// format parser did not provide one (lossless formats).
if audioProps.Bitrate == 0 && lengthMillis > 0 {
audioProps.Bitrate = int(
props.FileSize * 8 / lengthMillis,
)
}
return tags, lengthMillis, audioProps, timing, nil
}