From d8cae5b9c472cd2dcbeae3352ac922191f060013 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 8 Sep 2023 18:48:21 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Fingerprinting=20audio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/run.go | 15 ++++++++ config/config.go | 2 + media/fingerprint.go | 88 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 media/fingerprint.go diff --git a/app/run.go b/app/run.go index ee8324a..0b29c57 100644 --- a/app/run.go +++ b/app/run.go @@ -24,7 +24,22 @@ func quit() { os.Exit(0) } +func testFp() { + fp, err := media.Fingerprint("/home/drj/MyFiles/Videos/WebShows/YouTube/Dolly_Parton_-_Topic/Just_Because_I_m_a_Woman.Dolly_Parton_-_Topic.Fmv-XQerVkM.webm") + if err != nil { + panic(err) + } + // fmt.Printf("%+v\n", fp) + ids, err := media.LookupFingerprint(fp) + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", ids) + quit() +} + func Run(step AppStep) { + testFp() for step < Quit { switch step { case Pick: diff --git a/config/config.go b/config/config.go index 4ca54bc..493f7df 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,8 @@ type Config struct { FfEncoder string `toml:"ff_encoder"` AllowedCodecs []string `toml:"allowed_codec"` CodecExt map[string]string `toml:"codec_ext"` + AcousticIdKey string `toml:"acoustic_id_key"` + AcousticUserKey string `toml:"acoustic_user_key"` } var config Config diff --git a/media/fingerprint.go b/media/fingerprint.go new file mode 100644 index 0000000..9954110 --- /dev/null +++ b/media/fingerprint.go @@ -0,0 +1,88 @@ +package media + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + u "net/url" + "os/exec" + "strings" + + "codeberg.org/danjones000/strip-beats/config" +) + +type FPrint struct { + Duration float64 + Fingerprint string +} + +func Fingerprint(path string) (FPrint, error) { + pr := FPrint{} + _, err := exec.LookPath("fpcalc") + if err != nil { + return pr, errors.Join(errors.New("Unable to find fpcalc in PATH"), err) + } + cmd := exec.Command("fpcalc", "-json", path) + out, err := cmd.Output() + if err != nil { + return pr, errors.Join(errors.New(fmt.Sprintf("Failed to run %s", strings.Join(cmd.Args, " "))), err) + } + err = json.Unmarshal(out, &pr) + if err != nil { + return pr, errors.Join(errors.New("Couldn't parse output from fpcalc"), err) + } + + return pr, nil +} + +type IdResults struct { + Status string + Results []IdResult + Error IdError +} + +type IdResult struct { + Id string + Score float64 + Recordings []MbRecording +} + +type IdError struct { + Code int + Message string +} + +type MbRecording struct { + Id string +} + +func LookupFingerprint(print FPrint) (IdResults, error) { + res := IdResults{} + key := config.GetConfig().AcousticIdKey + if key == "" { + return res, errors.New("Missing acoustic_id_key from config") + } + url := "https://api.acoustid.org/v2/lookup" + form := u.Values{ + "format": {"json"}, + "client": {key}, + "duration": {fmt.Sprintf("%.0f", print.Duration)}, + "fingerprint": {print.Fingerprint}, + "meta": {"recordingids"}} + resp, err := http.PostForm(url, form) + if err != nil { + return res, err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return res, err + } + json.Unmarshal(body, &res) + if res.Status == "error" { + err = errors.New(res.Error.Message) + } + return res, err +}