Skip to content

Commit 7574861

Browse files
committed
Updated
1 parent e317ec1 commit 7574861

File tree

9 files changed

+183
-33
lines changed

9 files changed

+183
-33
lines changed

cmd/fingerprint/main.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
An example of fingerprinting audio and recognizing the any music tracks within the audio.
3+
*/
4+
package main
5+
6+
import (
7+
"encoding/binary"
8+
"errors"
9+
"flag"
10+
"fmt"
11+
"io"
12+
"os"
13+
"time"
14+
15+
// Packages
16+
chromaprint "github.com/mutablelogic/go-media/pkg/chromaprint"
17+
config "github.com/mutablelogic/go-media/pkg/config"
18+
)
19+
20+
var (
21+
flagVersion = flag.Bool("version", false, "Print version information")
22+
flagRate = flag.Int("rate", 22050, "Sample rate")
23+
flagChannels = flag.Int("channels", 1, "Number of channels")
24+
flagKey = flag.String("key", "${CHROMAPRINT_KEY}", "AcoustID API key")
25+
flagLength = flag.Duration("length", 2*time.Minute, "Restrict the duration of the processed input audio")
26+
flagDuration = flag.Duration("duration", 0, "The actual duration of the audio file")
27+
)
28+
29+
const (
30+
bufsize = 1024 * 64 // Number of bytes to read at a time
31+
)
32+
33+
func main() {
34+
flag.Parse()
35+
36+
// Check for version
37+
if *flagVersion {
38+
config.PrintVersion(flag.CommandLine.Output())
39+
chromaprint.PrintVersion(flag.CommandLine.Output())
40+
os.Exit(0)
41+
}
42+
43+
// Check arguments
44+
if flag.NArg() != 1 {
45+
flag.Usage()
46+
os.Exit(-1)
47+
}
48+
49+
// Open file
50+
info, err := os.Stat(flag.Arg(0))
51+
if err != nil {
52+
fmt.Fprintln(os.Stderr, err)
53+
os.Exit(-2)
54+
}
55+
if info.Size() == 0 {
56+
fmt.Fprintln(os.Stderr, "file is empty")
57+
os.Exit(-2)
58+
}
59+
60+
// Open the file
61+
r, err := os.Open(flag.Arg(0))
62+
if err != nil {
63+
fmt.Fprintln(os.Stderr, err)
64+
os.Exit(-2)
65+
}
66+
defer r.Close()
67+
68+
// Create fingerprinter, write samples
69+
size := int(info.Size() >> 1)
70+
fingerprint := chromaprint.New(*flagRate, *flagChannels, *flagLength)
71+
samples := make([]int16, bufsize)
72+
for {
73+
// Adjust buffer size
74+
sz := MinInt(bufsize, size)
75+
size -= sz
76+
if sz == 0 {
77+
break
78+
}
79+
80+
// Read samples
81+
if err := binary.Read(r, binary.LittleEndian, samples[:sz]); errors.Is(err, io.EOF) {
82+
break
83+
} else if err != nil {
84+
fmt.Fprintln(os.Stderr, "Unexpected error:", err)
85+
os.Exit(-2)
86+
}
87+
88+
// Write samples
89+
if _, err := fingerprint.Write(samples); err != nil {
90+
fmt.Fprintln(os.Stderr, err)
91+
os.Exit(-2)
92+
}
93+
}
94+
95+
// Get fingerprint
96+
str, err := fingerprint.Finish()
97+
if err != nil {
98+
fmt.Fprintln(os.Stderr, err)
99+
os.Exit(-2)
100+
}
101+
102+
// Get duration
103+
duration := fingerprint.Duration()
104+
if *flagDuration != 0 {
105+
duration = *flagDuration
106+
}
107+
108+
// Create client, make matches
109+
client := chromaprint.NewClient(*flagKey)
110+
if matches, err := client.Lookup(str, duration, chromaprint.META_ALL); err != nil {
111+
fmt.Fprintln(os.Stderr, err)
112+
os.Exit(-2)
113+
} else if len(matches) == 0 {
114+
fmt.Fprintln(os.Stderr, "No matches found")
115+
} else {
116+
for _, match := range matches {
117+
fmt.Println(match)
118+
}
119+
}
120+
}
121+
122+
func MinInt(a, b int) int {
123+
if a < b {
124+
return a
125+
}
126+
return b
127+
}

etc/test/audio_22050_1ch_5m35.s16le

2.52 MB
Binary file not shown.

etc/test/s16le_22050_1ch_audio.raw

-2.52 MB
Binary file not shown.

etc/test/s16le_44100_1ch_audio.raw

-5.05 MB
Binary file not shown.

etc/test/s16le_44100_2ch_audio.raw

-38.9 MB
Binary file not shown.

pkg/chromaprint/client.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"mime"
99
"net/http"
1010
"net/url"
11+
"os"
1112
"path/filepath"
1213
"time"
1314

@@ -19,10 +20,10 @@ import (
1920
// TYPES
2021

2122
type Config struct {
22-
Key string `yaml:key` // AcuostId Web Service Key
23-
Timeout time.Duration `yaml:timeout` // AcoustId Client timeout
24-
Rate uint `yaml:rate` // Maximum requests per second
25-
Base string `yaml:base` // Base URL
23+
Key string `yaml:"key"` // AcuostId Web Service Key
24+
Timeout time.Duration `yaml:"timeout"` // AcoustId Client timeout
25+
Rate uint `yaml:"rate"` // Maximum requests per second
26+
Base string `yaml:"base"` // Base URL
2627
}
2728

2829
type Client struct {
@@ -50,7 +51,7 @@ const (
5051
)
5152

5253
var (
53-
ErrQueryRateExceeded = errors.New("Query Rate Exceeded")
54+
ErrQueryRateExceeded = errors.New("query rate exceeded")
5455
)
5556

5657
var (
@@ -65,8 +66,12 @@ var (
6566
////////////////////////////////////////////////////////////////////////////////
6667
// NEW
6768

68-
func NewClient() *Client {
69-
if client, err := NewClientWithConfig(DefaultConfig); err != nil {
69+
func NewClient(key string) *Client {
70+
config := DefaultConfig
71+
if key != "" {
72+
config.Key = os.ExpandEnv(key)
73+
}
74+
if client, err := NewClientWithConfig(config); err != nil {
7075
return nil
7176
} else {
7277
return client
@@ -82,7 +87,7 @@ func NewClientWithConfig(cfg Config) (*Client, error) {
8287
client.Config.Timeout = DefaultConfig.Timeout
8388
}
8489
if client.Key == "" {
85-
client.Key = DefaultConfig.Key
90+
client.Key = os.ExpandEnv(DefaultConfig.Key)
8691
}
8792
if client.Base == "" {
8893
client.Base = DefaultConfig.Base
@@ -153,6 +158,8 @@ func (client *Client) Lookup(fingerprint string, duration time.Duration, flags M
153158
return nil, ErrBadParameter.With("Lookup")
154159
}
155160

161+
//fmt.Println(url.String())
162+
156163
// Perform request
157164
now := time.Now()
158165
req, err := http.NewRequest(http.MethodGet, url.String(), nil)

pkg/chromaprint/client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const (
2525
// TESTS
2626

2727
func Test_Client_001(t *testing.T) {
28-
client := NewClient()
28+
client := NewClient("${CHROMAPRINT_KEY}")
2929
if client == nil {
3030
t.Fatal("Failed to create client")
3131
} else {

pkg/chromaprint/fingerprint.go

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package chromaprint
22

33
import (
44
"io"
5+
"time"
56
"unsafe"
67

78
// Packages
@@ -11,30 +12,34 @@ import (
1112
////////////////////////////////////////////////////////////////////////////////
1213
// TYPES
1314

14-
// Chromaprint is a wrapper around the chromaprint library. Create a new
15-
// Chromaprint object by calling New(rate,channels)
16-
type Chromaprint interface {
17-
io.Closer
18-
19-
// Write sample data to the fingerprinter. Expects 16-bit signed integers
20-
// and returns number of samples written
21-
Write([]int16) (int64, error)
22-
23-
// Finish the fingerprinting, and compute the fingerprint, return as a
24-
// string
25-
Finish() (string, error)
26-
}
27-
2815
type fingerprint struct {
29-
n int64
30-
ctx *chromaprint.Context
16+
rate, channels int
17+
duration time.Duration
18+
n int64
19+
ctx *chromaprint.Context
3120
}
3221

22+
////////////////////////////////////////////////////////////////////////////////
23+
// CONSTANTS
24+
25+
const (
26+
defaultDuration = 2 * time.Minute
27+
)
28+
3329
////////////////////////////////////////////////////////////////////////////////
3430
// LIFECYCLE
3531

36-
// Create a new fingerprint context
37-
func New(rate, channels int) *fingerprint {
32+
// Create a new fingerprint context, with the expected sample rate,
33+
// number of channels and the maximum duration of the data to put into
34+
// the fingerprint. Returns nil if the context could not be created.
35+
func New(rate, channels int, duration time.Duration) *fingerprint {
36+
// Check arguments
37+
if rate <= 0 || channels <= 0 || duration <= 0 {
38+
return nil
39+
} else if duration == 0 {
40+
duration = defaultDuration
41+
}
42+
3843
// Create a context
3944
ctx := chromaprint.NewChromaprint(chromaprint.ALGORITHM_DEFAULT)
4045
if ctx == nil {
@@ -46,7 +51,7 @@ func New(rate, channels int) *fingerprint {
4651
return nil
4752
}
4853
// Return success
49-
return &fingerprint{0, ctx}
54+
return &fingerprint{rate, channels, duration, 0, ctx}
5055
}
5156

5257
// Close the fingerprint to release resources
@@ -72,8 +77,10 @@ func (fingerprint *fingerprint) Write(data []int16) (int64, error) {
7277
if fingerprint.ctx == nil {
7378
return 0, io.ErrClosedPipe
7479
}
75-
if err := fingerprint.ctx.WritePtr(uintptr(unsafe.Pointer(&data[0])), len(data)); err != nil {
76-
return 0, err
80+
if fingerprint.Duration() < fingerprint.duration {
81+
if err := fingerprint.ctx.WritePtr(uintptr(unsafe.Pointer(&data[0])), len(data)); err != nil {
82+
return 0, err
83+
}
7784
}
7885
fingerprint.n += int64(len(data))
7986
return fingerprint.n, nil
@@ -89,3 +96,11 @@ func (fingerprint *fingerprint) Finish() (string, error) {
8996
}
9097
return fingerprint.ctx.GetFingerprint()
9198
}
99+
100+
// Return the duration of the sampled data
101+
func (fingerprint *fingerprint) Duration() time.Duration {
102+
if fingerprint.ctx == nil {
103+
return 0
104+
}
105+
return time.Duration(fingerprint.n) * time.Second / time.Duration(fingerprint.rate*fingerprint.channels)
106+
}

pkg/chromaprint/fingerprint_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import (
55
"io"
66
"os"
77
"testing"
8+
"time"
89

910
"github.com/mutablelogic/go-media/pkg/chromaprint"
1011
"github.com/stretchr/testify/assert"
1112
)
1213

1314
const (
1415
// Test data
15-
testData1 = "../../etc/test/s16le_22050_1ch_audio.raw"
16+
testData1 = "../../etc/test/audio_22050_1ch_5m35.s16le"
1617
)
1718

1819
func Test_fingerprint_000(t *testing.T) {
@@ -21,14 +22,14 @@ func Test_fingerprint_000(t *testing.T) {
2122

2223
func Test_fingerprint_001(t *testing.T) {
2324
assert := assert.New(t)
24-
fingerprint := chromaprint.New(22050, 1)
25+
fingerprint := chromaprint.New(22050, 1, 5*time.Minute+35*time.Second)
2526
assert.NotNil(fingerprint)
2627
assert.NoError(fingerprint.Close())
2728
}
2829

2930
func Test_fingerprint_002(t *testing.T) {
3031
assert := assert.New(t)
31-
fingerprint := chromaprint.New(22050, 1)
32+
fingerprint := chromaprint.New(22050, 1, 5*time.Minute+35*time.Second)
3233
assert.NotNil(fingerprint)
3334

3435
r, err := os.Open(testData1)

0 commit comments

Comments
 (0)