package media import ( "encoding/binary" "encoding/json" "errors" "fmt" "io" "os" ) const SizeOfInt16 = 2 // MediaSet represents the media and metadata associated with a single media // resource (for example, a YouTube video). type MediaSet struct { ID string `json:"id"` Source string `json:"source"` Bytes int64 `json:"bytes"` Channels int `json:"channels"` Frames int64 `json:"frames"` SampleRate int `json:"sample_rate"` exists bool } func (m *MediaSet) RawAudioPath() string { return fmt.Sprintf("cache/%s.raw", m.ID) } func (m *MediaSet) EncodedAudioPath() string { return fmt.Sprintf("cache/%s.m4a", m.ID) } func (m *MediaSet) VideoPath() string { return fmt.Sprintf("cache/%s.mp4", m.ID) } func (m *MediaSet) MetadataPath() string { return fmt.Sprintf("cache/%s.json", m.ID) } func (m *MediaSet) Exists() bool { if m.exists { return true } if _, err := os.Stat(m.MetadataPath()); err == nil { m.exists = true return true } return false } func (m *MediaSet) Load() error { metadataFile, err := os.Open(m.MetadataPath()) if err != nil { return fmt.Errorf("error opening metadata file: %v", err) } defer func() { _ = metadataFile.Close() }() if err := json.NewDecoder(metadataFile).Decode(m); err != nil { return fmt.Errorf("error decoding metadata: %v", err) } return nil } func (m *MediaSet) Peaks(start, end int64, numBins int) ([][]int16, error) { if !m.Exists() { return nil, errors.New("cannot compute peaks for non-existent MediaSet") } var err error fptr, err := os.Open(m.RawAudioPath()) if err != nil { return nil, fmt.Errorf("audio open error: %v", err) } defer fptr.Close() startByte := start * int64(m.Channels) * SizeOfInt16 if _, err = fptr.Seek(startByte, io.SeekStart); err != nil { return nil, fmt.Errorf("audio seek error: %v", err) } numFrames := end - start framesPerBin := numFrames / int64(numBins) peaks := make([][]int16, m.Channels) for i := 0; i < m.Channels; i++ { peaks[i] = make([]int16, numBins) } samples := make([]int16, framesPerBin*int64(m.Channels)) for binNum := 0; binNum < numBins; binNum++ { if err := binary.Read(fptr, binary.LittleEndian, samples); err != nil { return nil, fmt.Errorf("error reading samples: %v", err) } for i, samp := range samples { if samp < 0 { samp = -samp } chanIndex := i % m.Channels if samp > peaks[chanIndex][binNum] { peaks[chanIndex][binNum] = samp } } } return peaks, nil }