Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ DOCKER=$(shell which docker)
FFMPEG_VERSION=ffmpeg-7.1.1
CHROMAPRINT_VERSION=chromaprint-1.5.1

# CGO configuration - set CGO vars for C++ libraries
CGO_ENV=PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" CGO_LDFLAGS="-lstdc++ -lavutil"

# Build flags
BUILD_MODULE := $(shell cat go.mod | head -1 | cut -d ' ' -f 2)
BUILD_LD_FLAGS += -X $(BUILD_MODULE)/pkg/version.GitSource=${BUILD_MODULE}
Expand All @@ -31,19 +34,19 @@ PREFIX ?= ${BUILD_DIR}/install
# TARGETS

.PHONY: all
all: clean ffmpeg cli
all: clean ffmpeg chromaprint cli

.PHONY: cmds
cmds: $(CMD_DIR)

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

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

###############################################################################
# FFMPEG
Expand Down Expand Up @@ -84,7 +87,7 @@ ffmpeg: ffmpeg-build
###############################################################################
# CHROMAPRINT

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

# Configure chromaprint
.PHONY: chromaprint-configure
chromaprint-configure: mkdir ${BUILD_DIR}/${CHROMAPRINT_VERSION}
chromaprint-configure: mkdir ${BUILD_DIR}/${CHROMAPRINT_VERSION} ffmpeg
@echo "Configuring ${CHROMAPRINT_VERSION} => ${PREFIX}"
cmake \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=0 \
-DBUILD_TESTS=0 \
-DBUILD_TOOLS=0 \
-DFFT_LIB=avfft \
-DCMAKE_PREFIX_PATH="$(shell realpath ${PREFIX})" \
--install-prefix "$(shell realpath ${PREFIX})" \
-S ${BUILD_DIR}/${CHROMAPRINT_VERSION} \
-B ${BUILD_DIR}
Expand All @@ -116,10 +121,13 @@ chromaprint-build: chromaprint-configure
@cd $(BUILD_DIR) && make -j2

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

###############################################################################
# DOCKER
Expand Down Expand Up @@ -148,11 +156,11 @@ test: test-ffmpeg
test-ffmpeg: go-dep go-tidy ffmpeg chromaprint
@echo Test
@echo ... test sys/ffmpeg71
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./sys/ffmpeg71
@${CGO_ENV} ${GO} test ./sys/ffmpeg71
@echo ... test pkg/segmenter
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/segmenter
@${CGO_ENV} ${GO} test ./pkg/segmenter
@echo ... test pkg/chromaprint
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/chromaprint
@${CGO_ENV} ${GO} test ./pkg/chromaprint


# @echo ... test pkg/ffmpeg
Expand All @@ -173,11 +181,11 @@ test-ffmpeg: go-dep go-tidy ffmpeg chromaprint
container-test: go-dep go-tidy ffmpeg chromaprint
@echo Test
@echo ... test sys/ffmpeg71
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./sys/ffmpeg71
@${CGO_ENV} ${GO} test ./sys/ffmpeg71
@echo ... test pkg/segmenter
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/segmenter
@${CGO_ENV} ${GO} test ./pkg/segmenter
@echo ... test pkg/chromaprint
@PKG_CONFIG_PATH="$(shell realpath ${PREFIX})/lib/pkgconfig" CGO_LDFLAGS_ALLOW="-(W|D).*" ${GO} test ./pkg/chromaprint
@${CGO_ENV} ${GO} test ./pkg/chromaprint

###############################################################################
# DEPENDENCIES, ETC
Expand Down Expand Up @@ -216,9 +224,9 @@ ffmpeg-dep:
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists freetype2 && echo "--enable-libfreetype"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists theora && echo "--enable-libtheora"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vorbis && echo "--enable-libvorbis"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vpx && echo "--enable-libvpx"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists opus && echo "--enable-libopus"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x264 && echo "--enable-libx264"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists x265 && echo "--enable-libx265"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists opus && echo "--enable-libopus"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists xvid && echo "--enable-libxvid"))
$(eval FFMPEG_CONFIG := $(FFMPEG_CONFIG) $(shell pkg-config --exists vpx && echo "--enable-libvpx"))
@echo "FFmpeg configuration: $(FFMPEG_CONFIG)"
94 changes: 94 additions & 0 deletions cmd/media/fingerprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"fmt"
"os"

// Packages
chromaprint "github.com/mutablelogic/go-media/pkg/chromaprint"
ffmpeg "github.com/mutablelogic/go-media/pkg/ffmpeg"
server "github.com/mutablelogic/go-server"
)

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

type FingerprintCommands struct {
MatchMusic MatchMusic `cmd:"" group:"MATCH" help:"Match Music Track"`
}

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"`
}

///////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS

func (cmd *MatchMusic) Run(app server.Cmd) error {
ffmpeg.SetLogging(false, nil)

// Create a client
client, err := chromaprint.NewClient(cmd.APIKey)
if err != nil {
return err
}

// Open the file
r, err := os.Open(cmd.Path)
if err != nil {
return err
}
defer r.Close()

var meta chromaprint.Meta
for _, t := range cmd.Type {
switch t {
case "any":
meta |= chromaprint.META_ALL
case "recording":
meta |= chromaprint.META_RECORDING
case "release":
meta |= chromaprint.META_RELEASE
case "releasegroup":
meta |= chromaprint.META_RELEASEGROUP
case "track":
meta |= chromaprint.META_TRACK
default:
return fmt.Errorf("unknown type %q", t)
}
}

// Create the matches
matches, err := client.Match(app.Context(), r, meta)
if err != nil {
return err
}

// Filter by score
result := make([]*chromaprint.ResponseMatch, 0, len(matches))
for _, m := range matches {
if m.Score >= cmd.Score {
result = append(result, m)
}
}

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
*/
1 change: 1 addition & 0 deletions cmd/media/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
type CLI struct {
MetadataCommands
CodecCommands
FingerprintCommands
VersionCommands
}

Expand Down
44 changes: 44 additions & 0 deletions cmd/media/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
type MetadataCommands struct {
Meta ListMetadata `cmd:"" group:"METADATA" help:"Examine metadata"`
Artwork ExtractArtwork `cmd:"" group:"METADATA" help:"Extract artwork"`
Streams ListStreams `cmd:"" group:"METADATA" help:"List streams"`
Thumbnails ExtractThumbnails `cmd:"" group:"METADATA" help:"Extract video thumbnails"`
}

Expand All @@ -38,6 +39,11 @@ type ExtractArtwork struct {
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}"`
}

type ListStreams struct {
Path string `arg:"" type:"path" help:"File or directory"`
Recursive bool `short:"r" help:"Recursively examine files"`
}

type ExtractThumbnails struct {
Path string `arg:"" type:"path" help:"File"`
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}"`
Expand All @@ -48,6 +54,44 @@ type ExtractThumbnails struct {
///////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS

func (cmd *ListStreams) Run(app server.Cmd) error {
// Create the media manager
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))
if err != nil {
return err
}

// Create a new file walker
walker := file.NewWalker(func(ctx context.Context, root, relpath string, info os.FileInfo) error {
if info.IsDir() {
if !cmd.Recursive && relpath != "." {
return file.SkipDir
}
return nil
}

// Open file
f, err := manager.Open(filepath.Join(root, relpath), nil)
if err != nil {
return fmt.Errorf("%s: %w", info.Name(), err)
}
defer f.Close()

// Enumerate streams
streams := f.(*ffmpeg.Reader).Streams(media.ANY)
result := make([]media.Metadata, 0, len(streams))
result = append(result, ffmpeg.NewMetadata("path", filepath.Join(root, relpath)))
for _, meta := range streams {
result = append(result, ffmpeg.NewMetadata(fmt.Sprint(meta.Index()), meta))
}

return write(os.Stdout, result, nil)
})

// Perform the walk, return any errors
return walker.Walk(app.Context(), cmd.Path)
}

func (cmd *ListMetadata) Run(app server.Cmd) error {
// Create the media manager
manager, err := ffmpeg.NewManager(ffmpeg.OptLog(false, nil))
Expand Down
7 changes: 5 additions & 2 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ type Err uint
// GLOBALS

const (
ErrBadParameter Err = http.StatusBadRequest
ErrInternalError Err = http.StatusInternalServerError
ErrBadParameter Err = http.StatusBadRequest
ErrInternalError Err = http.StatusInternalServerError
ErrNotImplemented Err = http.StatusNotImplemented
)

///////////////////////////////////////////////////////////////////////////////
Expand All @@ -31,6 +32,8 @@ func (code Err) Error() string {
return "bad parameter"
case ErrInternalError:
return "internal error"
case ErrNotImplemented:
return "not implemented"
default:
return fmt.Sprintf("error code %d", code.Code())
}
Expand Down
2 changes: 1 addition & 1 deletion etc/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ COPY . .

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

# Build all the commands
Expand Down
Loading
Loading