diff --git a/cmd/media/fingerprint.go b/cmd/media/fingerprint.go index 64b5208..b6a3999 100644 --- a/cmd/media/fingerprint.go +++ b/cmd/media/fingerprint.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "time" // Packages chromaprint "github.com/mutablelogic/go-media/pkg/chromaprint" @@ -18,10 +19,11 @@ type FingerprintCommands struct { } type MatchMusic struct { - Path string `arg:"" type:"path" help:"File"` - APIKey string `env:"CHROMAPRINT_KEY" help:"API key for the music matching service (https://acoustid.org/login)"` - Type []string `cmd:"" help:"Type of match to perform" enum:"any,recording,release,releasegroup,track" default:"any"` - Score float64 `cmd:"" help:"Minimum match scoreto perform" default:"0.9"` + Path string `arg:"" type:"path" help:"File"` + APIKey string `env:"CHROMAPRINT_KEY" help:"API key for the music matching service (https://acoustid.org/login)"` + Type []string `cmd:"" help:"Type of match to perform" enum:"any,recording,release,releasegroup,track" default:"any"` + Score float64 `cmd:"" help:"Minimum match scoreto perform" default:"0.9"` + Duration time.Duration `cmd:"" help:"Length of audio to use for fingerprinting" default:"30s"` } /////////////////////////////////////////////////////////////////////////////// @@ -62,7 +64,7 @@ func (cmd *MatchMusic) Run(app server.Cmd) error { } // Create the matches - matches, err := client.Match(app.Context(), r, meta) + matches, err := client.Match(app.Context(), r, cmd.Duration, meta) if err != nil { return err } @@ -78,17 +80,3 @@ func (cmd *MatchMusic) Run(app server.Cmd) error { fmt.Println(result) return nil } - -/* - - META_RECORDING Meta = (1 << iota) - META_RECORDINGID - META_RELEASE - META_RELEASEID - META_RELEASEGROUP - META_RELEASEGROUPID - META_TRACK - META_COMPRESS - META_USERMETA - META_SOURCE -*/ diff --git a/pkg/chromaprint/client.go b/pkg/chromaprint/client.go index 4e11915..07d649d 100644 --- a/pkg/chromaprint/client.go +++ b/pkg/chromaprint/client.go @@ -38,6 +38,9 @@ const ( // defaultQps rate limits number of requests per second defaultQps = 3 + + // sample rate used for fingerprinting + sampleRate = 32000 ) /////////////////////////////////////////////////////////////////////////////// @@ -94,10 +97,11 @@ func (c *Client) Lookup(fingerprint string, duration time.Duration, flags Meta) //////////////////////////////////////////////////////////////////////////////// // FINGERPRINT -// Match a media file and lookup any matches -func (c *Client) Match(ctx context.Context, r io.Reader, flags Meta) ([]*ResponseMatch, error) { +// Match a media file and lookup any matches, using up to "dur" seconds +// to fingerprint (or zero for no limit). The fingerprint is calculated +func (c *Client) Match(ctx context.Context, r io.Reader, dur time.Duration, flags Meta) ([]*ResponseMatch, error) { // Create a segmenter - segmenter, err := segmenter.NewReader(r, 0, 32000) + segmenter, err := segmenter.NewReader(r, dur, sampleRate) if err != nil { return nil, err } @@ -111,13 +115,17 @@ func (c *Client) Match(ctx context.Context, r io.Reader, flags Meta) ([]*Respons defer fp.Free() // Start the fingerprinting - if err := fp.Start(32000, 1); err != nil { + if err := fp.Start(sampleRate, 1); err != nil { return nil, err } - // Perform fingerprinting + // Perform fingerprinting. Segment the audio into 'dur' segments, only feed + // the fingerprinter when the timestamp is less than 'dur' if err := segmenter.DecodeInt16(ctx, func(timestamp time.Duration, data []int16) error { - return fp.WritePtr(uintptr(unsafe.Pointer(&data[0])), len(data)) + if dur == 0 || timestamp < dur { + return fp.WritePtr(uintptr(unsafe.Pointer(&data[0])), len(data)) + } + return nil }); err != nil { return nil, err } @@ -127,6 +135,7 @@ func (c *Client) Match(ctx context.Context, r io.Reader, flags Meta) ([]*Respons return nil, err } + // Get fingerprint value value, err := fp.GetFingerprint() if err != nil { return nil, err diff --git a/pkg/chromaprint/client_test.go b/pkg/chromaprint/client_test.go index 4917b0b..bed931e 100644 --- a/pkg/chromaprint/client_test.go +++ b/pkg/chromaprint/client_test.go @@ -89,7 +89,7 @@ func Test_client_004(t *testing.T) { } defer r.Close() - matches, err := client.Match(context.Background(), r, META_ALL) + matches, err := client.Match(context.Background(), r, 0, META_ALL) assert.NoError(err) t.Log(matches) } diff --git a/pkg/segmenter/segmenter.go b/pkg/segmenter/segmenter.go index 4b5fc6b..464e05e 100644 --- a/pkg/segmenter/segmenter.go +++ b/pkg/segmenter/segmenter.go @@ -87,7 +87,7 @@ func (s *Segmenter) Close() error { // Return current timestamp func (s *Segmenter) Duration() time.Duration { - return s.ts + return s.reader.Duration() } // Segments are output through a callback, with the samples and a timestamp @@ -109,7 +109,7 @@ func (s *Segmenter) DecodeFloat32(ctx context.Context, fn SegmentFuncFloat32) er // Allocate the buffer if s.n > 0 { - s.buf_flt = make([]float32, s.n) + s.buf_flt = make([]float32, 0, s.n) } // Decode samples and segment @@ -174,7 +174,7 @@ func (s *Segmenter) DecodeInt16(ctx context.Context, fn SegmentFuncInt16) error // Allocate the buffer if s.n > 0 { - s.buf_s16 = make([]int16, s.n) + s.buf_s16 = make([]int16, 0, s.n) } // Decode samples and segment