package utils
import (
"database/sql"
"fmt"
"path/filepath"
"time"
)
type TimestampResult struct {
Timestamp time.Time
IsAudioMoth bool
MothData *AudioMothData
}
func ResolveTimestamp(wavMeta *WAVMetadata, filePath string, timezoneID string, useFileModTime bool) (*TimestampResult, error) {
result := &TimestampResult{}
if IsAudioMoth(wavMeta.Comment, wavMeta.Artist) {
result.IsAudioMoth = true
mothData, err := ParseAudioMothComment(wavMeta.Comment)
if err == nil {
result.MothData = mothData
result.Timestamp = mothData.Timestamp
return result, nil
}
}
if HasTimestampFilename(filePath) {
filenameTimestamps, err := ParseFilenameTimestamps([]string{filepath.Base(filePath)})
if err == nil {
adjustedTimestamps, err := ApplyTimezoneOffset(filenameTimestamps, timezoneID)
if err == nil && len(adjustedTimestamps) > 0 {
result.Timestamp = adjustedTimestamps[0]
return result, nil
}
}
}
if useFileModTime && !wavMeta.FileModTime.IsZero() {
result.Timestamp = wavMeta.FileModTime
return result, nil
}
return nil, fmt.Errorf("cannot resolve timestamp (no AudioMoth, filename pattern, or file modification time)")
}
type FileProcessingResult struct {
FileName string
Hash string
Duration float64
SampleRate int
TimestampLocal time.Time
IsAudioMoth bool
MothData *AudioMothData
AstroData AstronomicalData
}
func ProcessSingleFile(filePath string, latitude, longitude float64, timezoneID string, useFileModTime bool) (*FileProcessingResult, error) {
metadata, err := ParseWAVHeader(filePath)
if err != nil {
return nil, fmt.Errorf("WAV header parsing failed: %w", err)
}
hash, err := ComputeXXH64(filePath)
if err != nil {
return nil, fmt.Errorf("hash calculation failed: %w", err)
}
tsResult, err := ResolveTimestamp(metadata, filePath, timezoneID, useFileModTime)
if err != nil {
return nil, err
}
astroData := CalculateAstronomicalData(
tsResult.Timestamp.UTC(),
metadata.Duration,
latitude,
longitude,
)
return &FileProcessingResult{
FileName: filepath.Base(filePath),
Hash: hash,
Duration: metadata.Duration,
SampleRate: metadata.SampleRate,
TimestampLocal: tsResult.Timestamp,
IsAudioMoth: tsResult.IsAudioMoth,
MothData: tsResult.MothData,
AstroData: astroData,
}, nil
}
type DBQueryable interface {
QueryRow(query string, args ...interface{}) *sql.Row
}
func CheckDuplicateHash(q DBQueryable, hash string) (existingID string, isDuplicate bool, err error) {
err = q.QueryRow(
"SELECT id FROM file WHERE xxh64_hash = ? AND active = true",
hash,
).Scan(&existingID)
if err == nil {
return existingID, true, nil
}
if err == sql.ErrNoRows {
return "", false, nil
}
return "", false, fmt.Errorf("duplicate check failed: %w", err)
}