Skip to content

Commit

Permalink
Merge pull request #21 from mutablelogic/ffmpeg61
Browse files Browse the repository at this point in the history
Added chromaprint client
  • Loading branch information
djthorpe authored Jun 23, 2024
2 parents 35a34fe + e41a36d commit 6455059
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 209 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ test: go-dep
@echo Test
@${GO} mod tidy
@${GO} test ./sys/ffmpeg61
@${GO} test ./sys/chromaprint
@${GO} test ./pkg/...
@${GO} test .

Expand Down
4 changes: 3 additions & 1 deletion decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,9 @@ FOR_LOOP:

// Flush the decoders
for _, decoder := range d.decoders {
if err := decoder.decode(nil, demuxfn, framefn); err != nil {
if err := decoder.decode(nil, demuxfn, framefn); errors.Is(err, io.EOF) {
// no-op
} else if err != nil {
return err
}
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/alecthomas/kong v0.9.0
github.com/djthorpe/go-errors v1.0.3
github.com/djthorpe/go-tablewriter v0.0.8
github.com/mutablelogic/go-client v1.0.8
github.com/stretchr/testify v1.9.0
)

Expand Down
20 changes: 14 additions & 6 deletions manager.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package media

import (
"fmt"
"io"
"runtime"

Expand Down Expand Up @@ -311,13 +312,13 @@ func (manager *manager) Write(w io.Writer, format Format, metadata []Metadata, p
// Return version information for the media manager as a set of metadata
func (manager *manager) Version() []Metadata {
metadata := []Metadata{
newMetadata("libavcodec_version", ff.AVCodec_version()),
newMetadata("libavformat_version", ff.AVFormat_version()),
newMetadata("libavutil_version", ff.AVUtil_version()),
newMetadata("libavdevice_version", ff.AVDevice_version()),
newMetadata("libavcodec_version", ffVersionAsString(ff.AVCodec_version())),
newMetadata("libavformat_version", ffVersionAsString(ff.AVFormat_version())),
newMetadata("libavutil_version", ffVersionAsString(ff.AVUtil_version())),
newMetadata("libavdevice_version", ffVersionAsString(ff.AVDevice_version())),
// newMetadata("libavfilter_version", ff.AVFilter_version()),
newMetadata("libswscale_version", ff.SWScale_version()),
newMetadata("libswresample_version", ff.SWResample_version()),
newMetadata("libswscale_version", ffVersionAsString(ff.SWScale_version())),
newMetadata("libswresample_version", ffVersionAsString(ff.SWResample_version())),
}
if version.GitSource != "" {
metadata = append(metadata, newMetadata("git_source", version.GitSource))
Expand Down Expand Up @@ -352,3 +353,10 @@ func (manager *manager) Warningf(f string, args ...any) {
func (manager *manager) Infof(f string, args ...any) {
ff.AVUtil_log(nil, ff.AV_LOG_INFO, f, args...)
}

////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func ffVersionAsString(version uint) string {
return fmt.Sprintf("%d.%d.%d", version&0xFF0000>>16, version&0xFF00>>8, version&0xFF)
}
3 changes: 2 additions & 1 deletion metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type metadata struct {
}

const (
MetaArtwork = "artwork"
MetaArtwork = "artwork" // Metadata key for artwork, sets the value as []byte
MetaDuration = "duration" // Metadata key for duration, sets the value as float64 (seconds)
)

////////////////////////////////////////////////////////////////////////////////
Expand Down
203 changes: 32 additions & 171 deletions pkg/chromaprint/client.go
Original file line number Diff line number Diff line change
@@ -1,225 +1,86 @@
package chromaprint

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"path/filepath"
"time"

// Packages
"github.com/mutablelogic/go-client"

// Namespace imports
. "github.com/djthorpe/go-errors"
)

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// TYPES

type Config struct {
Key string `yaml:"key"` // AcuostId Web Service Key
Timeout time.Duration `yaml:"timeout"` // AcoustId Client timeout
Rate uint `yaml:"rate"` // Maximum requests per second
Base string `yaml:"base"` // Base URL
}

type Client struct {
Config
*http.Client
*url.URL
last time.Time
*client.Client
key string
}

////////////////////////////////////////////////////////////////////////////////
// GLOBALS

const (
// Register a clientId: https://acoustid.org/login
defaultClientId = "-YectPtndAM"

// Timeout requests after 15 seconds
defaultTimeout = 15 * time.Second
defaultApiKey = "-YectPtndAM"

// The API endpoint
baseUrl = "https://api.acoustid.org/v2"
endPoint = "https://api.acoustid.org/v2"

// defaultQps rate limits number of requests per second
defaultQps = 3
)

var (
ErrQueryRateExceeded = errors.New("query rate exceeded")
)
///////////////////////////////////////////////////////////////////////////////
// LIFECYCLE

var (
DefaultConfig = Config{
Key: defaultClientId,
Timeout: defaultTimeout,
Rate: defaultQps,
Base: baseUrl,
// Create a new client
func NewClient(ApiKey string, opts ...client.ClientOpt) (*Client, error) {
// Check for missing API key
if ApiKey == "" {
ApiKey = defaultApiKey
}
)

////////////////////////////////////////////////////////////////////////////////
// NEW

func NewClient(key string) *Client {
config := DefaultConfig
if key != "" {
config.Key = os.ExpandEnv(key)
}
if client, err := NewClientWithConfig(config); err != nil {
return nil
} else {
return client
}
}

func NewClientWithConfig(cfg Config) (*Client, error) {
client := new(Client)
client.Config = cfg

// Set parameters
if client.Config.Timeout == 0 {
client.Config.Timeout = DefaultConfig.Timeout
}
if client.Key == "" {
client.Key = os.ExpandEnv(DefaultConfig.Key)
}
if client.Base == "" {
client.Base = DefaultConfig.Base
}
if client.Rate == 0 {
client.Rate = DefaultConfig.Rate
}

// Create HTTP client
client.Client = &http.Client{
Timeout: client.Config.Timeout,
}
url, err := url.Parse(client.Base)
// Create client
opts = append(opts, client.OptEndpoint(endPoint))
client, err := client.New(opts...)
if err != nil {
return nil, err
} else {
client.URL = url
}
// Fudge key into URL
v := url.Query()
v.Set("client", client.Key)
url.RawQuery = v.Encode()

// Return success
return client, nil
}

////////////////////////////////////////////////////////////////////////////////
// STRINGIFY

func (client *Client) String() string {
str := "<chromaprint"
if u := client.URL; u != nil {
str += fmt.Sprintf(" url=%q", u.String())
}
if rate := client.Rate; rate > 0 {
str += fmt.Sprintf(" rate=%dops/s", rate)
}
if timeout := client.Client.Timeout; timeout > 0 {
str += fmt.Sprintf(" timeout=%v", timeout)
}
return str + ">"
// Return the client
return &Client{
Client: client,
key: ApiKey,
}, nil
}

////////////////////////////////////////////////////////////////////////////////
// LOOKUP

func (client *Client) Lookup(fingerprint string, duration time.Duration, flags Meta) ([]*ResponseMatch, error) {
// Lookup a fingerprint with a duration and the metadata that needs to be
// returned
func (c *Client) Lookup(fingerprint string, duration time.Duration, flags Meta) ([]*ResponseMatch, error) {
// Check incoming parameters
if fingerprint == "" || duration == 0 || flags == META_NONE {
return nil, ErrBadParameter.With("Lookup")
}

// Check Qps
if client.last.IsZero() {
if time.Since(client.last) < (time.Second / defaultQps) {
return nil, ErrQueryRateExceeded
}
}

// Set URL parameters
params := url.Values{}
params.Set("client", c.key)
params.Set("fingerprint", fingerprint)
params.Set("duration", fmt.Sprint(duration.Truncate(time.Second).Seconds()))
params.Set("meta", flags.String())
url := client.requestUrl("lookup", params)
if url == nil {
return nil, ErrBadParameter.With("Lookup")
}

//fmt.Println(url.String())

// Perform request
now := time.Now()
req, err := http.NewRequest(http.MethodGet, url.String(), nil)
if err != nil {
// Request -> Response
var response Response
if err := c.Do(nil, &response, client.OptPath("lookup"), client.OptQuery(params)); err != nil {
return nil, err
} else {
return response.Results, nil
}
response, err := client.Client.Do(req)
if err != nil {
return nil, err
}
defer response.Body.Close()

// Read response body
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}

// Decode response body
var r Response
if mimeType, _, err := mime.ParseMediaType(response.Header.Get("Content-type")); err != nil {
return nil, ErrUnexpectedResponse.With(err)
} else if mimeType != "application/json" {
return nil, ErrUnexpectedResponse.With(mimeType)
} else if err := json.Unmarshal(body, &r); err != nil {
return nil, ErrUnexpectedResponse.With(err)
}

// Check for errors
if r.Status != "ok" {
return nil, ErrBadParameter.Withf("%v (code %v)", r.Error.Message, r.Error.Code)
} else if response.StatusCode != http.StatusOK {
return nil, ErrBadParameter.Withf("%v (code %v)", response.Status, response.StatusCode)
}

// Set response time for calculating qps
client.last = now

// Return success
return r.Results, nil
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func (client *Client) requestUrl(path string, v url.Values) *url.URL {
url, err := url.Parse(client.URL.String())
if err != nil {
return nil
}
// Copy params
params := client.URL.Query()
for k := range v {
params[k] = v[k]
}
url.RawQuery = params.Encode()

// Set path
url.Path = filepath.Join(url.Path, path)

// Return URL
return url
}
Loading

0 comments on commit 6455059

Please sign in to comment.