Skip to content

Commit d05aee3

Browse files
authored
Merge pull request #44 from mutablelogic/ffmpeg71
Added Music Matching through chromaprint
2 parents 8abfa6c + 70374be commit d05aee3

File tree

19 files changed

+397
-104
lines changed

19 files changed

+397
-104
lines changed

Makefile

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ DOCKER=$(shell which docker)
66
FFMPEG_VERSION=ffmpeg-7.1.1
77
CHROMAPRINT_VERSION=chromaprint-1.5.1
88

9+
# CGO configuration - set CGO vars for C++ libraries
10+
CGO_ENV=PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" CGO_LDFLAGS="-lstdc++ -lavutil"
11+
912
# Build flags
1013
BUILD_MODULE := $(shell cat go.mod | head -1 | cut -d ' ' -f 2)
1114
BUILD_LD_FLAGS += -X $(BUILD_MODULE)/pkg/version.GitSource=${BUILD_MODULE}
@@ -31,19 +34,19 @@ PREFIX ?= ${BUILD_DIR}/install
3134
# TARGETS
3235

3336
.PHONY: all
34-
all: clean ffmpeg cli
37+
all: clean ffmpeg chromaprint cli
3538

3639
.PHONY: cmds
3740
cmds: $(CMD_DIR)
3841

3942
.PHONY: cli
4043
cli: go-dep go-tidy mkdir
4144
@echo Build media tool
42-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/media ./cmd/media
45+
@${CGO_ENV} ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/media ./cmd/media
4346

4447
$(CMD_DIR): go-dep go-tidy mkdir
4548
@echo Build cmd $(notdir $@)
46-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/$(notdir $@) ./$@
49+
@${CGO_ENV} ${GO} build ${BUILD_FLAGS} -o ${BUILD_DIR}/$(notdir $@) ./$@
4750

4851
###############################################################################
4952
# FFMPEG
@@ -84,7 +87,7 @@ ffmpeg: ffmpeg-build
8487
###############################################################################
8588
# CHROMAPRINT
8689

87-
# Download ffmpeg sources
90+
# Download chromaprint sources
8891
${BUILD_DIR}/${CHROMAPRINT_VERSION}:
8992
@if [ ! -d "$(BUILD_DIR)/$(CHROMAPRINT_VERSION)" ]; then \
9093
echo "Downloading $(CHROMAPRINT_VERSION)"; \
@@ -97,14 +100,16 @@ ${BUILD_DIR}/${CHROMAPRINT_VERSION}:
97100

98101
# Configure chromaprint
99102
.PHONY: chromaprint-configure
100-
chromaprint-configure: mkdir ${BUILD_DIR}/${CHROMAPRINT_VERSION}
103+
chromaprint-configure: mkdir ${BUILD_DIR}/${CHROMAPRINT_VERSION} ffmpeg
101104
@echo "Configuring ${CHROMAPRINT_VERSION} => ${PREFIX}"
102105
cmake \
103106
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
104107
-DCMAKE_BUILD_TYPE=Release \
105108
-DBUILD_SHARED_LIBS=0 \
106109
-DBUILD_TESTS=0 \
107110
-DBUILD_TOOLS=0 \
111+
-DFFT_LIB=avfft \
112+
-DCMAKE_PREFIX_PATH="$(shell realpath ${PREFIX})" \
108113
--install-prefix "$(shell realpath ${PREFIX})" \
109114
-S ${BUILD_DIR}/${CHROMAPRINT_VERSION} \
110115
-B ${BUILD_DIR}
@@ -116,10 +121,13 @@ chromaprint-build: chromaprint-configure
116121
@cd $(BUILD_DIR) && make -j2
117122

118123
# Install chromaprint
124+
# Create a modified pkg-config file that ensures correct linking order for C++
119125
.PHONY: chromaprint
120126
chromaprint: chromaprint-build
121127
@echo "Installing ${CHROMAPRINT_VERSION} => ${PREFIX}"
122128
@cd $(BUILD_DIR) && make install
129+
@sed -i.bak 's/Libs: -L\${libdir} -lchromaprint/Libs: -L\${libdir} -lchromaprint -lstdc++ -lavutil/g' "${PREFIX}/lib/pkgconfig/libchromaprint.pc"
130+
@rm -f "${PREFIX}/lib/pkgconfig/libchromaprint.pc.bak"
123131

124132
###############################################################################
125133
# DOCKER
@@ -148,11 +156,11 @@ test: test-ffmpeg
148156
test-ffmpeg: go-dep go-tidy ffmpeg chromaprint
149157
@echo Test
150158
@echo ... test sys/ffmpeg71
151-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./sys/ffmpeg71
159+
@${CGO_ENV} ${GO} test ./sys/ffmpeg71
152160
@echo ... test pkg/segmenter
153-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/segmenter
161+
@${CGO_ENV} ${GO} test ./pkg/segmenter
154162
@echo ... test pkg/chromaprint
155-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/chromaprint
163+
@${CGO_ENV} ${GO} test ./pkg/chromaprint
156164

157165

158166
# @echo ... test pkg/ffmpeg
@@ -173,11 +181,11 @@ test-ffmpeg: go-dep go-tidy ffmpeg chromaprint
173181
container-test: go-dep go-tidy ffmpeg chromaprint
174182
@echo Test
175183
@echo ... test sys/ffmpeg71
176-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./sys/ffmpeg71
184+
@${CGO_ENV} ${GO} test ./sys/ffmpeg71
177185
@echo ... test pkg/segmenter
178-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/segmenter
186+
@${CGO_ENV} ${GO} test ./pkg/segmenter
179187
@echo ... test pkg/chromaprint
180-
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/chromaprint
188+
@${CGO_ENV} ${GO} test ./pkg/chromaprint
181189

182190
###############################################################################
183191
# DEPENDENCIES, ETC
@@ -216,9 +224,9 @@ ffmpeg-dep:
216224
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists freetype2 && echo "--enable-libfreetype"))
217225
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists theora && echo "--enable-libtheora"))
218226
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vorbis && echo "--enable-libvorbis"))
219-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vpx && echo "--enable-libvpx"))
227+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists opus && echo "--enable-libopus"))
220228
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x264 && echo "--enable-libx264"))
221229
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x265 && echo "--enable-libx265"))
222-
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists opus && echo "--enable-libopus"))
223230
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists xvid && echo "--enable-libxvid"))
231+
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vpx && echo "--enable-libvpx"))
224232
@echo "FFmpeg configuration: $(FFMPEG_CONFIG)"

cmd/media/fingerprint.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
// Packages
8+
chromaprint "github.com/mutablelogic/go-media/pkg/chromaprint"
9+
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
10+
server "github.com/mutablelogic/go-server"
11+
)
12+
13+
///////////////////////////////////////////////////////////////////////////////
14+
// TYPES
15+
16+
type FingerprintCommands struct {
17+
MatchMusic MatchMusic `cmd:"" group:"MATCH" help:"Match Music Track"`
18+
}
19+
20+
type MatchMusic struct {
21+
Path string `arg:"" type:"path" help:"File"`
22+
APIKey string `env:"CHROMAPRINT_KEY" help:"API key for the music matching service (https://acoustid.org/login)"`
23+
Type []string `cmd:"" help:"Type of match to perform" enum:"any,recording,release,releasegroup,track" default:"any"`
24+
Score float64 `cmd:"" help:"Minimum match scoreto perform" default:"0.9"`
25+
}
26+
27+
///////////////////////////////////////////////////////////////////////////////
28+
// PUBLIC METHODS
29+
30+
func (cmd *MatchMusic) Run(app server.Cmd) error {
31+
ffmpeg.SetLogging(false, nil)
32+
33+
// Create a client
34+
client, err := chromaprint.NewClient(cmd.APIKey)
35+
if err != nil {
36+
return err
37+
}
38+
39+
// Open the file
40+
r, err := os.Open(cmd.Path)
41+
if err != nil {
42+
return err
43+
}
44+
defer r.Close()
45+
46+
var meta chromaprint.Meta
47+
for _, t := range cmd.Type {
48+
switch t {
49+
case "any":
50+
meta |= chromaprint.META_ALL
51+
case "recording":
52+
meta |= chromaprint.META_RECORDING
53+
case "release":
54+
meta |= chromaprint.META_RELEASE
55+
case "releasegroup":
56+
meta |= chromaprint.META_RELEASEGROUP
57+
case "track":
58+
meta |= chromaprint.META_TRACK
59+
default:
60+
return fmt.Errorf("unknown type %q", t)
61+
}
62+
}
63+
64+
// Create the matches
65+
matches, err := client.Match(app.Context(), r, meta)
66+
if err != nil {
67+
return err
68+
}
69+
70+
// Filter by score
71+
result := make([]*chromaprint.ResponseMatch, 0, len(matches))
72+
for _, m := range matches {
73+
if m.Score >= cmd.Score {
74+
result = append(result, m)
75+
}
76+
}
77+
78+
fmt.Println(result)
79+
return nil
80+
}
81+
82+
/*
83+
84+
META_RECORDING Meta = (1 << iota)
85+
META_RECORDINGID
86+
META_RELEASE
87+
META_RELEASEID
88+
META_RELEASEGROUP
89+
META_RELEASEGROUPID
90+
META_TRACK
91+
META_COMPRESS
92+
META_USERMETA
93+
META_SOURCE
94+
*/

cmd/media/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
type CLI struct {
1515
MetadataCommands
1616
CodecCommands
17+
FingerprintCommands
1718
VersionCommands
1819
}
1920

cmd/media/metadata.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
type MetadataCommands struct {
2525
Meta ListMetadata `cmd:"" group:"METADATA" help:"Examine metadata"`
2626
Artwork ExtractArtwork `cmd:"" group:"METADATA" help:"Extract artwork"`
27+
Streams ListStreams `cmd:"" group:"METADATA" help:"List streams"`
2728
Thumbnails ExtractThumbnails `cmd:"" group:"METADATA" help:"Extract video thumbnails"`
2829
}
2930

@@ -38,6 +39,11 @@ type ExtractArtwork struct {
3839
Out string `required:"" help:"Output filename for artwork, relative to the source path. Use {count} {hash} {path} {name} or {ext} for placeholders" default:"{hash}{ext}"`
3940
}
4041

42+
type ListStreams struct {
43+
Path string `arg:"" type:"path" help:"File or directory"`
44+
Recursive bool `short:"r" help:"Recursively examine files"`
45+
}
46+
4147
type ExtractThumbnails struct {
4248
Path string `arg:"" type:"path" help:"File"`
4349
Out string `required:"" help:"Output filename for thumbnail, relative to the source path. Use {timestamp} {frame} {path} {name} or {ext} for placeholders" default:"{frame}{ext}"`
@@ -48,6 +54,44 @@ type ExtractThumbnails struct {
4854
///////////////////////////////////////////////////////////////////////////////
4955
// PUBLIC METHODS
5056

57+
func (cmd *ListStreams) Run(app server.Cmd) error {
58+
// Create the media manager
59+
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))
60+
if err != nil {
61+
return err
62+
}
63+
64+
// Create a new file walker
65+
walker := file.NewWalker(func(ctx context.Context, root, relpath string, info os.FileInfo) error {
66+
if info.IsDir() {
67+
if !cmd.Recursive && relpath != "." {
68+
return file.SkipDir
69+
}
70+
return nil
71+
}
72+
73+
// Open file
74+
f, err := manager.Open(filepath.Join(root, relpath), nil)
75+
if err != nil {
76+
return fmt.Errorf("%s: %w", info.Name(), err)
77+
}
78+
defer f.Close()
79+
80+
// Enumerate streams
81+
streams := f.(*ffmpeg.Reader).Streams(media.ANY)
82+
result := make([]media.Metadata, 0, len(streams))
83+
result = append(result, ffmpeg.NewMetadata("path", filepath.Join(root, relpath)))
84+
for _, meta := range streams {
85+
result = append(result, ffmpeg.NewMetadata(fmt.Sprint(meta.Index()), meta))
86+
}
87+
88+
return write(os.Stdout, result, nil)
89+
})
90+
91+
// Perform the walk, return any errors
92+
return walker.Walk(app.Context(), cmd.Path)
93+
}
94+
5195
func (cmd *ListMetadata) Run(app server.Cmd) error {
5296
// Create the media manager
5397
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))

error.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ type Err uint
1414
// GLOBALS
1515

1616
const (
17-
ErrBadParameter Err = http.StatusBadRequest
18-
ErrInternalError Err = http.StatusInternalServerError
17+
ErrBadParameter Err = http.StatusBadRequest
18+
ErrInternalError Err = http.StatusInternalServerError
19+
ErrNotImplemented Err = http.StatusNotImplemented
1920
)
2021

2122
///////////////////////////////////////////////////////////////////////////////
@@ -31,6 +32,8 @@ func (code Err) Error() string {
3132
return "bad parameter"
3233
case ErrInternalError:
3334
return "internal error"
35+
case ErrNotImplemented:
36+
return "not implemented"
3437
default:
3538
return fmt.Sprintf("error code %d", code.Code())
3639
}

etc/docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ COPY . .
1010

1111
# Install dependencies
1212
RUN set -x && apt update -y \
13-
&& apt install -y ca-certificates lsb-release nasm curl \
13+
&& apt install -y ca-certificates lsb-release build-essential cmake nasm curl \
1414
&& apt install -y libfreetype-dev libmp3lame-dev libopus-dev libvorbis-dev libvpx-dev libx264-dev libx265-dev libnuma-dev
1515

1616
# Build all the commands

0 commit comments

Comments
 (0)