Initial commit.

This commit is contained in:
Chigozirim Igweamaka 2024-03-05 17:25:27 +01:00
parent 2873cbe948
commit 1689cc42b5
15 changed files with 2396 additions and 0 deletions

84
go.mod Normal file
View file

@ -0,0 +1,84 @@
module song-recognition
go 1.21.6
require (
cloud.google.com/go/compute v1.23.4 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/adrg/strutil v0.3.1 // indirect
github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/giorgisio/goav v0.1.0 // indirect
github.com/go-audio/audio v1.0.0 // indirect
github.com/go-audio/riff v1.0.0 // indirect
github.com/go-audio/wav v1.1.0 // indirect
github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff // indirect
github.com/go-fingerprint/gochroma v0.0.0-20211004000611-a294aa5ccab6 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.1 // indirect
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
github.com/kkdai/youtube/v2 v2.10.0 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pion/datachannel v1.5.5 // indirect
github.com/pion/dtls/v2 v2.2.10 // indirect
github.com/pion/ice/v3 v3.0.3 // indirect
github.com/pion/interceptor v0.1.25 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.12 // indirect
github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.13 // indirect
github.com/pion/rtp v1.8.3 // indirect
github.com/pion/sctp v1.8.12 // indirect
github.com/pion/sdp/v3 v3.0.6 // indirect
github.com/pion/srtp/v3 v3.0.1 // indirect
github.com/pion/stun/v2 v2.0.0 // indirect
github.com/pion/transport/v2 v2.2.4 // indirect
github.com/pion/transport/v3 v3.0.1 // indirect
github.com/pion/turn/v3 v3.0.1 // indirect
github.com/pion/webrtc/v4 v4.0.0-beta.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/raitonoberu/ytmusic v0.0.0-20220927155833-3d1de71caa11 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect
go.opentelemetry.io/otel v1.23.0 // indirect
go.opentelemetry.io/otel/metric v1.23.0 // indirect
go.opentelemetry.io/otel/trace v1.23.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/api v0.166.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect
google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

386
go.sum Normal file
View file

@ -0,0 +1,386 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw=
cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ZekeLu/go-mp3 v0.3.5-pre h1:D2Ttzfp/ZazLKVryN0Hv0kyuKbej0ofU1Cagwu+e8zA=
github.com/ZekeLu/go-mp3 v0.3.5-pre/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/giorgisio/goav v0.1.0 h1:ZyfG3NfX7PMSimv4ulhmnQJf/XeHpMdGCn+afRmY5Oc=
github.com/giorgisio/goav v0.1.0/go.mod h1:RtH8HyxLRLU1iY0pjfhWBKRhnbsnmfoI+FxMwb5bfEo=
github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g=
github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE=
github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff h1:MVMRAz9+9uI8+nzBPHILUVLNzH+jaKKmGmEf5GrgNsM=
github.com/go-fingerprint/fingerprint v0.0.0-20140803133125-29397256b7ff/go.mod h1:p+iFTUBRUOKBOZtWQCQAZHhLI7fC5bMdiDc5B4PPBU0=
github.com/go-fingerprint/gochroma v0.0.0-20211004000611-a294aa5ccab6 h1:ofe4/jf63isEPwFgoy81WKWzuA3gfegrthdJXgdXKJI=
github.com/go-fingerprint/gochroma v0.0.0-20211004000611-a294aa5ccab6/go.mod h1:zsgLdL2ov2nW56GWAEjcSp75I6ZIZjxnToJGZ/ouYwQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.12.1 h1:9F8GV9r9ztXyAi00gsMQHNoF51xPZm8uj1dpYt2ZETM=
github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc=
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo=
github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/kkdai/youtube/v2 v2.10.0 h1:s8gSWo3AxIafK560XwDVnha9aPXp3N2HQAh1x81R5Og=
github.com/kkdai/youtube/v2 v2.10.0/go.mod h1:H5MLUXiXYuovcEhQT/uZf7BC/syIbAJlDKCDsG+WDsU=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pion/datachannel v1.5.5 h1:10ef4kwdjije+M9d7Xm9im2Y3O6A6ccQb0zcqZcJew8=
github.com/pion/datachannel v1.5.5/go.mod h1:iMz+lECmfdCMqFRhXhcA/219B0SQlbpoR2V118yimL0=
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA=
github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
github.com/pion/ice/v3 v3.0.3 h1:Mu5QkZ2pYmcjq9JETDcDR7F8UzjP1VHmcZmgU0yqsyk=
github.com/pion/ice/v3 v3.0.3/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q=
github.com/pion/interceptor v0.1.25 h1:pwY9r7P6ToQ3+IF0bajN0xmk/fNw/suTgaTdlwTDmhc=
github.com/pion/interceptor v0.1.25/go.mod h1:wkbPYAak5zKsfpVDYMtEfWEy8D4zL+rpxCxPImLOg3Y=
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
github.com/pion/mdns v0.0.12 h1:CiMYlY+O0azojWDmxdNr7ADGrnZ+V6Ilfner+6mSVK8=
github.com/pion/mdns v0.0.12/go.mod h1:VExJjv8to/6Wqm1FXK+Ii/Z9tsVk/F5sD/N70cnYFbk=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.10/go.mod h1:ztfEwXZNLGyF1oQDttz/ZKIBaeeg/oWbRYqzBM9TL1I=
github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtcp v1.2.13 h1:+EQijuisKwm/8VBs8nWllr0bIndR7Lf7cZG200mpbNo=
github.com/pion/rtcp v1.2.13/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4=
github.com/pion/rtp v1.8.2/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/rtp v1.8.3 h1:VEHxqzSVQxCkKDSHro5/4IUUG1ea+MFdqR2R3xSpNU8=
github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU=
github.com/pion/sctp v1.8.5/go.mod h1:SUFFfDpViyKejTAdwD1d/HQsCu+V/40cCs2nZIvC3s0=
github.com/pion/sctp v1.8.12 h1:2VX50pedElH+is6FI+OKyRTeN5oy4mrk2HjnGa3UCmY=
github.com/pion/sctp v1.8.12/go.mod h1:cMLT45jqw3+jiJCrtHVwfQLnfR0MGZ4rgOJwUOIqLkI=
github.com/pion/sdp/v3 v3.0.6 h1:WuDLhtuFUUVpTfus9ILC4HRyHsW6TdugjEX/QY9OiUw=
github.com/pion/sdp/v3 v3.0.6/go.mod h1:iiFWFpQO8Fy3S5ldclBkpXqmWy02ns78NOKoLLL0YQw=
github.com/pion/srtp/v3 v3.0.1 h1:AkIQRIZ+3tAOJMQ7G301xtrD1vekQbNeRO7eY1K8ZHk=
github.com/pion/srtp/v3 v3.0.1/go.mod h1:3R3a1qIOIxBkVTLGFjafKK6/fJoTdQDhcC67HOyMbJ8=
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
github.com/pion/transport v0.14.1 h1:XSM6olwW+o8J4SCmOBb/BpwZypkHeyM0PGFCxNQBr40=
github.com/pion/transport v0.14.1/go.mod h1:4tGmbk00NeYA3rUa9+n+dzCCoKkcy3YlYb99Jn2fNnI=
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo=
github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0=
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
github.com/pion/turn/v3 v3.0.1 h1:wLi7BTQr6/Q20R0vt/lHbjv6y4GChFtC33nkYbasoT8=
github.com/pion/turn/v3 v3.0.1/go.mod h1:MrJDKgqryDyWy1/4NT9TWfXWGMC7UHT6pJIv1+gMeNE=
github.com/pion/webrtc/v4 v4.0.0-beta.9 h1:xmTVa6aia4fzOSP4Ki/hB7dKKtcIqaPI6YSfGDa5JZE=
github.com/pion/webrtc/v4 v4.0.0-beta.9/go.mod h1:z/hdYIuZUz2MFSdPKf099qRVAyTJwvy2c0nwRItCgZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/raitonoberu/ytmusic v0.0.0-20220927155833-3d1de71caa11 h1:jpddPIqdeF+TxOT1Zzd1u+k9AiVT4XJX/VuyZUnrgZE=
github.com/raitonoberu/ytmusic v0.0.0-20220927155833-3d1de71caa11/go.mod h1:hgP4hPl8kmhAaMjuaxxqKnHa7yA9UkXw4KY97XLyjRs=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA=
go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E=
go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0=
go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo=
go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo=
go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI=
go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.166.0 h1:6m4NUwrZYhAaVIHZWxaKjw1L1vNAjtMwORmKRyEEo24=
google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240205150955-31a09d347014 h1:g/4bk7P6TPMkAUbUhquq98xey1slwvuVJPosdBqYJlU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 h1:hZB7eLIaYlW9qXRfCq/qDaPdbeY3757uARz5Vvfv+cY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

64
imain.go Normal file
View file

@ -0,0 +1,64 @@
package main
import (
"fmt"
"path/filepath"
"song-recognition/shazam"
"song-recognition/spotify"
"song-recognition/utils"
"strings"
)
func matchSong(songPath string) error {
m4aFileMono := strings.TrimSuffix(songPath, filepath.Ext(songPath)) + "_mono.m4a"
audioBytes, err := spotify.ConvertM4aToMono(songPath, m4aFileMono)
if err != nil {
return fmt.Errorf("error converting M4A file to mono: %v", err)
}
chunks := shazam.Chunkify(audioBytes)
fingerpints, _ := shazam.FingerprintChunks(chunks, nil)
for _, fingerprint := range fingerpints {
db, err := utils.NewDbClient()
if err != nil {
return fmt.Errorf("error connecting to DB: %d", err)
}
chunkData, err := db.GetChunkData(fingerprint)
if err != nil {
return fmt.Errorf("error retrieving chunk data: %d", err)
}
fmt.Println("CHUNK DATA: ", chunkData)
}
return nil
}
func imain() {
// Example usage
// Open the MP3 file
// mp3FilePath := "spotifydown.com - These Are The Days.mp3"
// signal.Process_and_SaveSong(mp3FilePath, "These Are The Days", "lauren Daigle")
// https://open.spotify.com/track/3vnKyPnHMunE1bMXYQHFHU?si=34a43de5712c4331 - heaven has come
// https://open.spotify.com/track/7zwSMMJkrRJNvxFO9w42nA?si=fa7cef0f7bd14904 - we raise a sound Nosa and 121SELAH
// https://open.spotify.com/track/52WA7y6ACfdHbzIii6M9iA?si=8aa26d3974394645 - these are the days
// spotify.DlSingleTrack("https://open.spotify.com/track/3vnKyPnHMunE1bMXYQHFHU?si=34a43de5712c4331",
// "/home/chigozirim/Documents/my-docs/song-recognition/songs/")
// spotify.DlSingleTrack("https://open.spotify.com/track/7zwSMMJkrRJNvxFO9w42nA?si=fa7cef0f7bd14904",
// "/home/chigozirim/Documents/my-docs/song-recognition/songs/")
// spotify.DlSingleTrack("https://open.spotify.com/track/52WA7y6ACfdHbzIii6M9iA?si=8aa26d3974394645",
// "/home/chigozirim/Documents/my-docs/song-recognition/songs/")
spotify.DlPlaylist("https://open.spotify.com/playlist/7EAqBCOVkDZcbccjxZmgjp?si=bbc07260fb784861",
"/home/chigozirim/Documents/my-docs/song-recognition/songs/")
// err := matchSong("/home/chigozirim/Documents/my-docs/song-recognition/songs/We Raise A Sound - Nosa.m4a")
// if err != nil {
// fmt.Println("error matching song: ", err)
// return
// }
}

277
main.go Normal file
View file

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
//go:build !js
// +build !js
// save-to-disk is a simple application that shows how to record your webcam/microphone using Pion WebRTC and save VP8/Opus to disk.
package main
import (
"fmt"
"io"
"os"
"strings"
"time"
"github.com/pion/interceptor"
"github.com/pion/interceptor/pkg/intervalpli"
"github.com/pion/webrtc/v4"
"song-recognition/shazam"
"song-recognition/signal"
"github.com/pion/webrtc/v4/pkg/media"
"github.com/pion/webrtc/v4/pkg/media/ivfwriter"
"github.com/pion/webrtc/v4/pkg/media/oggwriter"
)
func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
defer func() {
if err := i.Close(); err != nil {
panic(err)
}
}()
for {
rtpPacket, _, err := track.ReadRTP()
if err != nil {
fmt.Println(err)
return
}
if err := i.WriteRTP(rtpPacket); err != nil {
fmt.Println(err)
return
}
}
}
func saveToBytes(track *webrtc.TrackRemote) ([]byte, error) {
var audioData []byte
for {
rtpPacket, _, err := track.ReadRTP()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
// Extract audio payload from RTP packet
payload := rtpPacket.Payload
// Append audio payload to audioData
audioData = append(audioData, payload...)
// fmt.Println("ByteArray: ", audioData)
}
return audioData, nil
}
func matchSampleAudio(track *webrtc.TrackRemote) (string, error) {
// Use time.After to stop after 15 seconds
stop := time.After(50 * time.Second)
// Use a ticker to process sampleAudio every 2 seconds
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
var sampleAudio []byte
for {
select {
case <-ticker.C:
// Process sampleAudio every 2 seconds
if len(sampleAudio) > 0 {
match, err := shazam.Match(sampleAudio)
if err != nil {
fmt.Println(err)
return "", nil
}
// Reset sampleAudio for fresh input
// sampleAudio = nil
if len(match) > 0 {
fmt.Println("FOUND A MATCH! - ", match)
return match, nil
}
}
case <-stop:
// Stop after 15 seconds
fmt.Println("Stopped after 15 seconds")
return "", nil
default:
// Read RTP packets and accumulate sampleAudio
rtpPacket, _, err := track.ReadRTP()
if err != nil {
if err != io.EOF {
return "", fmt.Errorf("error reading RTP packet: %d", err)
}
return "", err
}
// Extract audio payload from RTP packet
payload := rtpPacket.Payload
sampleAudio = append(sampleAudio, payload...)
}
}
}
// nolint:gocognit
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll use a VP8 and Opus but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 44100, Channels: 1, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 111,
}, webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
}
// Create a InterceptorRegistry. This is the user configurable RTP/RTCP Pipeline.
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
// This makes our video seekable and more error resilent, but at a cost of lower picture quality and higher bitrates
// A real world application should process incoming RTCP packets from viewers and forward them to senders
intervalPliFactory, err := intervalpli.NewReceiverInterceptor()
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
// Prepare the configuration
config := webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
},
}
// Create a new RTCPeerConnection
peerConnection, err := api.NewPeerConnection(config)
if err != nil {
panic(err)
}
// Allow us to receive 1 audio track, and 1 video track
if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
} else if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
// oggFile, err := oggwriter.New("output.ogg", 48000, 2)
oggFile, err := oggwriter.New("output.ogg", 44100, 1)
if err != nil {
panic(err)
}
ivfFile, err := ivfwriter.New("output.ivf")
if err != nil {
panic(err)
}
// Set a handler for when a new remote track starts, this handler saves buffers to disk as
// an ivf file, since we could have multiple video tracks we provide a counter.
// In your application this is where you would handle/process video
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
codec := track.Codec()
if strings.EqualFold(codec.MimeType, webrtc.MimeTypeOpus) {
fmt.Println("Got Opus track, saving to disk as output.opus (48 kHz, 2 channels)")
// matchSampleAudio(track)
saveToDisk(oggFile, track)
} else if strings.EqualFold(codec.MimeType, webrtc.MimeTypeVP8) {
fmt.Println("Got VP8 track, saving to disk as output.ivf")
saveToDisk(ivfFile, track)
}
})
// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
if connectionState == webrtc.ICEConnectionStateConnected {
fmt.Println("Ctrl+C the remote client to stop the demo")
} else if connectionState == webrtc.ICEConnectionStateFailed || connectionState == webrtc.ICEConnectionStateClosed {
if closeErr := oggFile.Close(); closeErr != nil {
panic(closeErr)
}
if closeErr := ivfFile.Close(); closeErr != nil {
panic(closeErr)
}
fmt.Println("Done writing media files")
// Gracefully shutdown the peer connection
if closeErr := peerConnection.Close(); closeErr != nil {
panic(closeErr)
}
os.Exit(0)
}
})
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
signal.Decode(signal.MustReadStdin(), &offer)
// Set the remote SessionDescription
err = peerConnection.SetRemoteDescription(offer)
if err != nil {
panic(err)
}
// Create answer
answer, err := peerConnection.CreateAnswer(nil)
if err != nil {
panic(err)
}
// Create channel that is blocked until ICE Gathering is complete
gatherComplete := webrtc.GatheringCompletePromise(peerConnection)
// Sets the LocalDescription, and starts our UDP listeners
err = peerConnection.SetLocalDescription(answer)
if err != nil {
panic(err)
}
// Block until ICE Gathering is complete, disabling trickle ICE
// we do this because we only can exchange one signaling message
// in a production application you should exchange ICE Candidates via OnICECandidate
<-gatherComplete
// Output the answer in base64 so we can paste it in browser
fmt.Println(signal.Encode(*peerConnection.LocalDescription()))
// Block forever
select {}
}

40
shazam/fft.go Normal file
View file

@ -0,0 +1,40 @@
package shazam
import (
"math"
"math/cmplx"
)
// fft performs the Fast Fourier Transform on the input signal.
func Fft(complexArray []complex128) []complex128 {
fftResult := make([]complex128, len(complexArray))
copy(fftResult, complexArray) // Copy input to result buffer
return recursiveFFT(fftResult)
}
// recursiveFFT performs the recursive FFT algorithm.
func recursiveFFT(complexArray []complex128) []complex128 {
N := len(complexArray)
if N <= 1 {
return complexArray
}
even := make([]complex128, N/2)
odd := make([]complex128, N/2)
for i := 0; i < N/2; i++ {
even[i] = complexArray[2*i]
odd[i] = complexArray[2*i+1]
}
even = recursiveFFT(even)
odd = recursiveFFT(odd)
fftResult := make([]complex128, N)
for k := 0; k < N/2; k++ {
t := cmplx.Exp(-2i * math.Pi * complex(float64(k), 0) / complex(float64(N), 0))
fftResult[k] = even[k] + t*odd[k]
fftResult[k+N/2] = even[k] - t*odd[k]
}
return fftResult
}

264
shazam/shazam.go Normal file
View file

@ -0,0 +1,264 @@
package shazam
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math"
"math/cmplx"
"math/rand"
"song-recognition/utils"
"sort"
"time"
)
// Constants
const (
chunkSize = 4096 // 4KB
fuzzFactor = 2
bitDepth = 2
channels = 1
samplingRate = 44100
)
// AudioInfo contains details about the audio data.
type AudioInfo struct {
SongName string
SongArtist string
BitDepth int
Channels int
SamplingRate int
TimeStamp string // TimeStamp for the chunk
}
func Match(sampleAudio []byte) (string, error) {
sampleChunks := Chunkify(sampleAudio)
chunkFingerprints, _ := FingerprintChunks(sampleChunks, nil)
db, err := utils.NewDbClient()
if err != nil {
return "", fmt.Errorf("error connecting to DB: %d", err)
}
defer db.Close()
var results = make(map[string][]string)
for _, chunkfgp := range chunkFingerprints {
listOfChunkData, err := db.GetChunkData(chunkfgp)
if err != nil {
return "", fmt.Errorf("error getting chunk data with fingerpring %d: %v", chunkfgp, err)
}
for _, chunkData := range listOfChunkData {
timeStamp := fmt.Sprint(chunkData["timestamp"])
songKey := fmt.Sprintf("%s by %s", chunkData["songname"], chunkData["songartist"])
if results[songKey] == nil {
results[songKey] = []string{timeStamp}
} else {
results[songKey] = append(results[songKey], timeStamp)
}
}
}
fmt.Println("Results: ", results)
maxMatchCount := 0
var maxMatch string
for songKey, timestamps := range results {
differences, err := timeDifference(timestamps)
if err != nil && err.Error() == "insufficient timestamps" {
continue
} else if err != nil {
return "", err
}
fmt.Printf("%s DIFFERENCES: %d\n", songKey, differences)
if len(differences) >= 2 {
if len(differences) > maxMatchCount {
maxMatchCount = len(differences)
maxMatch = songKey
}
}
}
fmt.Println("MATCH: ", maxMatch)
return "", nil
}
func timeDifference(timestamps []string) ([]int, error) {
if len(timestamps) < 2 {
return nil, fmt.Errorf("insufficient timestamps")
}
layout := "15:04:05"
timestampsInSeconds := make([]int, len(timestamps))
for i, ts := range timestamps {
parsedTime, err := time.Parse(layout, ts)
if err != nil {
return nil, fmt.Errorf("error parsing timestamp %q: %w", ts, err)
}
hours := parsedTime.Hour()
minutes := parsedTime.Minute()
seconds := parsedTime.Second()
timestampsInSeconds[i] = (hours * 3600) + (minutes * 60) + seconds
}
sort.Ints(timestampsInSeconds)
fmt.Println("timeStampsInSeconds: ", timestampsInSeconds)
differences := []int{}
for i := len(timestampsInSeconds) - 1; i >= 1; i-- {
difference := timestampsInSeconds[i] - timestampsInSeconds[i-1]
// maxSeconds = 15
if difference > 0 && difference <= 15 {
differences = append(differences, difference)
}
}
return differences, nil
}
// Chunkify divides the input audio data into chunks of bytes.
// It converts each byte in each chunk to a complex number, performs FFT on each
// chunk and returns the FFT results as a slice of slices of complex128.
func Chunkify(audio []byte) [][]complex128 {
totalSize := len(audio)
totalChunksInAudio := totalSize / chunkSize
chunks := make([][]complex128, totalChunksInAudio) // Slice of complex arrays
for i := 0; i < totalChunksInAudio; i++ {
complexArray := make([]complex128, chunkSize) // Initialize a complex array for each chunk
for j := 0; j < chunkSize; j++ {
// convert each byte in chunk to a complex number
b := audio[(i*chunkSize)+j]
complexArray[j] = complex(float64(b), 0)
}
chunks[i] = Fft(complexArray)
}
return chunks
}
// FingerprintChunks processes a collection of audio data represented as chunks of complex numbers and
// generates fingerprints for each chunk based on the magnitude of frequency components within specific frequency ranges.
func FingerprintChunks(chunks [][]complex128, audioInfo *AudioInfo) ([]int64, map[int64]AudioInfo) {
var fingerprintList []int64
fingerprintMap := make(map[int64]AudioInfo)
var bytesPerSecond, chunksPerSecond int
var chunkCount int
var chunkTime time.Time
if audioInfo != nil {
bytesPerSecond = (samplingRate * bitDepth * channels) / 8
chunksPerSecond = bytesPerSecond / chunkSize
// if chunkSize == 4096 {
// chunksPerSecond = 10
// }
chunkCount = 0
chunkTime = time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
}
for _, chunk := range chunks {
if audioInfo != nil {
chunkCount++
if chunkCount == chunksPerSecond {
chunkCount = 0
chunkTime = chunkTime.Add(1 * time.Second)
// fmt.Println(chunkTime.Format("15:04:05"))
}
}
chunkMags := map[string]int{
"20-60": 0, "60-250": 0, "250-500": 0,
"500-2000": 0, "2000-4000": 0, "4000-8000": 0, "8000-20000": 0,
}
for _, frequency := range chunk {
magnitude := int(cmplx.Abs(frequency))
ranges := []struct{ min, max int }{{20, 60}, {60, 250}, {250, 500}, {500, 2000}, {2000, 4000}, {4000, 8000}, {8000, 20001}}
for _, r := range ranges {
if magnitude >= r.min && magnitude < r.max &&
chunkMags[fmt.Sprintf("%d-%d", r.min, r.max)] < magnitude {
chunkMags[fmt.Sprintf("%d-%d", r.min, r.max)] = magnitude
}
}
}
// fingerprint := fmt.Sprintf("%d-%d-%d-%d-%d-%d-%d",
// chunkMags["20-60"],
// chunkMags["60-250"],
// chunkMags["250-500"],
// chunkMags["500-2000"],
// chunkMags["2000-4000"],
// chunkMags["4000-8000"],
// chunkMags["8000-20000"])
// fmt.Println(fingerprint)
points := [4]int64{
int64(chunkMags["60-250"]),
int64(chunkMags["250-500"]),
int64(chunkMags["500-2000"]),
int64(chunkMags["2000-4000"])}
key := hash1(points[:])
// fmt.Printf("%s: %v\n", fingerprint, key)
if audioInfo != nil {
newAudioInfo := *audioInfo
newAudioInfo.TimeStamp = chunkTime.Format("15:04:05")
fingerprintMap[key] = newAudioInfo
} else {
fingerprintList = append(fingerprintList, key)
}
}
return fingerprintList, fingerprintMap
}
func hash(values []int64) int64 {
if len(values) != 7 {
return 0 // Handle invalid input length
}
var result int64
for i := 0; i < len(values); i++ {
roundedValue := values[i] - (values[i] % fuzzFactor)
weight := int64(math.Pow10(len(values) - i - 1))
result += roundedValue * weight
}
return result
}
func hash1(values []int64) int64 {
p1, p2, p3, p4 := values[0], values[1], values[2], values[3]
return (p4-(p4%fuzzFactor))*100000000 +
(p3-(p3%fuzzFactor))*100000 +
(p2-(p2%fuzzFactor))*100 +
(p1 - (p1 % fuzzFactor))
}
func hash2(values []int64) int64 {
for i := range values {
values[i] += rand.Int63n(fuzzFactor) - fuzzFactor/2
}
var buf []byte
for _, v := range values {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(v))
buf = append(buf, b...)
}
hash := sha256.Sum256(buf)
return int64(binary.BigEndian.Uint64(hash[:8]))
}

0
signal/base64.txt Normal file
View file

31
signal/http.go Normal file
View file

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package signal
import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
)
// HTTPSDPServer starts a HTTP Server that consumes SDPs
func HTTPSDPServer(port int) chan string {
sdpChan := make(chan string)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
fmt.Fprintf(w, "done")
sdpChan <- string(body)
})
go func() {
// nolint: gosec
err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
if err != nil {
panic(err)
}
}()
return sdpChan
}

19
signal/rand.go Normal file
View file

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
package signal
import "github.com/pion/randutil"
// RandSeq generates a random string to serve as dummy data
//
// It returns a deterministic sequence of values each time a program is run.
// Use rand.Seed() function in your real applications.
func RandSeq(n int) string {
val, err := randutil.GenerateCryptoRandomString(n, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
if err != nil {
panic(err)
}
return val
}

121
signal/signal.go Normal file
View file

@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// Package signal contains helpers to exchange the SDP session
// description between examples.
package signal
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
)
// Allows compressing offer/answer to bypass terminal input limits.
const compress = false
// MustReadStdin blocks until input is received from stdin
func MustReadStdin() string {
filename := "/home/chigozirim/Documents/my-docs/song-recognition/signal/base64.txt"
file, err := os.Open(filename)
if err != nil {
panic(err)
}
defer file.Close()
r := bufio.NewReaderSize(file, 16384)
var in string
for {
var err error
in, err = r.ReadString('\n')
if err != io.EOF {
if err != nil {
panic(err)
}
}
in = strings.TrimSpace(in)
if len(in) > 0 {
break
}
}
fmt.Println("")
return in
}
// Encode encodes the input in base64
// It can optionally zip the input before encoding
func Encode(obj interface{}) string {
b, err := json.Marshal(obj)
if err != nil {
panic(err)
}
if compress {
b = zip(b)
}
return base64.StdEncoding.EncodeToString(b)
}
// Decode decodes the input from base64
// It can optionally unzip the input after decoding
func Decode(in string, obj interface{}) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
panic(err)
}
if compress {
b = unzip(b)
}
err = json.Unmarshal(b, obj)
if err != nil {
panic(err)
}
}
func zip(in []byte) []byte {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
_, err := gz.Write(in)
if err != nil {
panic(err)
}
err = gz.Flush()
if err != nil {
panic(err)
}
err = gz.Close()
if err != nil {
panic(err)
}
return b.Bytes()
}
func unzip(in []byte) []byte {
var b bytes.Buffer
_, err := b.Write(in)
if err != nil {
panic(err)
}
r, err := gzip.NewReader(&b)
if err != nil {
panic(err)
}
res, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
return res
}

373
spotify/downloader.go Normal file
View file

@ -0,0 +1,373 @@
package spotify
import (
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"song-recognition/shazam"
"song-recognition/utils"
"strconv"
"strings"
"sync"
"time"
"github.com/fatih/color"
"github.com/kkdai/youtube/v2"
)
var yellow = color.New(color.FgYellow)
func DlSingleTrack(url, savePath string) error {
trackInfo, err := TrackInfo(url)
if err != nil {
return err
}
fmt.Println("Getting track info...")
time.Sleep(500 * time.Millisecond)
track := []Track{*trackInfo}
fmt.Println("Now, downloading track...")
err = dlTrack(track, savePath)
if err != nil {
return err
}
return nil
}
func DlPlaylist(url, savePath string) error {
tracks, err := PlaylistInfo(url)
if err != nil {
return err
}
time.Sleep(1 * time.Second)
fmt.Println("Now, downloading playlist...")
err = dlTrack(tracks, savePath)
if err != nil {
fmt.Println(err)
return err
}
return nil
}
func dlAlbum(url, savePath string) error {
tracks, err := AlbumInfo(url)
if err != nil {
return err
}
time.Sleep(1 * time.Second)
fmt.Println("Now, downloading album...")
err = dlTrack(tracks, savePath)
if err != nil {
return err
}
return nil
}
func dlTrack(tracks []Track, path string) error {
var wg sync.WaitGroup
var totalTracks int
results := make(chan int, len(tracks))
numCPUs := runtime.NumCPU()
semaphore := make(chan struct{}, numCPUs)
for _, t := range tracks {
wg.Add(1)
go func(track Track) {
defer wg.Done()
semaphore <- struct{}{}
defer func() {
<-semaphore
}()
trackCopy := &Track{
Title: track.Title,
Artist: track.Artist,
Album: track.Album,
}
id, err := VideoID(*trackCopy)
if id == "" || err != nil {
yellow.Printf("Error (1): '%s' by '%s' could not be downloaded\n", trackCopy.Title, trackCopy.Artist)
return
}
trackCopy.Title, trackCopy.Artist = correctFilename(trackCopy.Title, trackCopy.Artist)
err = getAudio(id, path, trackCopy.Title, trackCopy.Artist)
if err != nil {
yellow.Printf("Error (2): '%s' by '%s' could not be downloaded\n", trackCopy.Title, trackCopy.Artist)
return
}
// Process and save audio
filename := fmt.Sprintf("%s - %s.m4a", trackCopy.Title, trackCopy.Artist)
route := filepath.Join(path, filename)
err = processAndSaveSong(route, trackCopy.Title, trackCopy.Artist)
if err != nil {
yellow.Println("Error processing audio: ", err)
}
trackCopy.Title, trackCopy.Artist = correctFilename(trackCopy.Title, trackCopy.Artist)
filePath := fmt.Sprintf("%s%s - %s.m4a", path, trackCopy.Title, trackCopy.Artist)
if err := addTags(filePath, *trackCopy); err != nil {
yellow.Println("Error adding tags: ", filePath)
return
}
size, _ := GetFileSize(filePath)
if size < 1 {
DeleteFile(filePath)
}
fmt.Printf("'%s' by '%s' was downloaded\n", track.Title, track.Artist)
results <- 1
}(t)
}
go func() {
wg.Wait()
close(results)
}()
for result := range results {
totalTracks += result
}
fmt.Println("Total tracks downloaded:", totalTracks)
return nil
}
/* github.com/kkdai/youtube */
func getAudio(id, path, title, artist string) error {
dir, err := os.Stat(path)
if err != nil {
panic(err)
}
if !dir.IsDir() {
return errors.New("the path is not valid (not a dir)")
}
db, err := utils.NewDbClient()
if err != nil {
return fmt.Errorf("error connecting to DB: %d", err)
}
defer db.Close()
// Check if the song has been processed and saved before
songKey := fmt.Sprintf("%s - %s", title, artist)
songExists, err := db.SongExists(songKey)
if err != nil {
return err
}
if songExists {
fmt.Println("Song exists: ", songKey)
return nil
}
client := youtube.Client{}
video, err := client.GetVideo(id)
if err != nil {
return err
}
/* itag code: 140, container: m4a, content: audio, bitrate: 128k */
/* change the FindByItag parameter to 139 if you want smaller files (but with a bitrate of 48k) */
formats := video.Formats.Itag(140)
filename := fmt.Sprintf("%s - %s.m4a", title, artist)
route := filepath.Join(path, filename)
/* in some cases, when attempting to download the audio
using the library github.com/kkdai/youtube,
the download fails (and shows the file size as 0 bytes)
until the second or third attempt. */
var fileSize int64
file, err := os.Create(route)
if err != nil {
return err
}
for fileSize == 0 {
stream, _, err := client.GetStream(video, &formats[0])
if err != nil {
return err
}
if _, err = io.Copy(file, stream); err != nil {
return err
}
fileSize, _ = GetFileSize(route)
}
defer file.Close()
return nil
}
func saveAudioToFile(audioReader io.Reader, path, title, artist string) error {
dir, err := os.Stat(path)
if err != nil {
panic(err)
}
if !dir.IsDir() {
return errors.New("the path is not valid (not a dir)")
}
filename := fmt.Sprintf("%s - %s.m4a", title, artist)
route := filepath.Join(path, filename)
/* in some cases, when attempting to download the audio
using the library github.com/kkdai/youtube,
the download fails (and shows the file size as 0 bytes)
until the second or third attempt. */
file, err := os.Create(route)
if err != nil {
return err
}
defer file.Close()
// Copy the audio stream to the file
_, err = io.Copy(file, audioReader)
if err != nil {
return err
}
return nil
}
func addTags(file string, track Track) error {
tempFile := file
index := strings.Index(file, ".m4a")
if index != -1 {
result := tempFile[:index] /* filename but with no extension ('/path/to/title - artist') */
tempFile = result + "2" + ".m4a" /* just a temporary dumb name ('/path/to/title - artist2.m4a') */
}
cmd := exec.Command(
"ffmpeg",
"-i", file, /* /path/to/title - artist.m4a */
"-c", "copy",
"-metadata", fmt.Sprintf("album_artist=%s", track.Artist),
"-metadata", fmt.Sprintf("title=%s", track.Title),
"-metadata", fmt.Sprintf("artist=%s", track.Artist),
"-metadata", fmt.Sprintf("album=%s", track.Album),
tempFile, /* /path/to/title - artist2.m4a */
)
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("ERROR FROM CMD:", err)
fmt.Println("FFMPEG Output:", string(out))
return err
}
// if err := cmd.Run(); err != nil {
// fmt.Println("ERROR FROM CMD: ", err)
// return err
// }
/* removes '2' from file name */
if err := os.Rename(tempFile, file); err != nil {
return err
}
return nil
}
/* fixes some invalid file names (windows is the capricious one) */
func correctFilename(title, artist string) (string, string) {
if runtime.GOOS == "windows" {
invalidChars := []byte{'<', '>', '<', ':', '"', '\\', '/', '|', '?', '*'}
for _, invalidChar := range invalidChars {
title = strings.ReplaceAll(title, string(invalidChar), "")
artist = strings.ReplaceAll(artist, string(invalidChar), "")
}
} else {
title = strings.ReplaceAll(title, "/", "\\")
artist = strings.ReplaceAll(artist, "/", "\\")
}
return title, artist
}
func processAndSaveSong(m4aFile, songName, songArtist string) error {
db, err := utils.NewDbClient()
if err != nil {
return fmt.Errorf("error connecting to DB: %d", err)
}
defer db.Close()
// Check if the song has been processed and saved before
songKey := fmt.Sprintf("%s - %s", songName, songArtist)
songExists, err := db.SongExists(songKey)
if err != nil {
return err
}
if songExists {
fmt.Println("Song exists: ", songKey)
return fmt.Errorf("error querying existing songs: %v", err)
}
// Convert M4A file to mono
m4aFileMono := strings.TrimSuffix(m4aFile, filepath.Ext(m4aFile)) + "_mono.m4a"
defer os.Remove(m4aFileMono) // Ensure the temporary output file is deleted
audioBytes, err := ConvertM4aToMono(m4aFile, m4aFileMono)
if err != nil {
return fmt.Errorf("error converting M4A file to mono: %v", err)
}
// Run ffprobe to get metadata of the input file
cmd := exec.Command("ffprobe", "-v", "error", "-select_streams", "a:0", "-show_entries", "stream=bit_depth,sample_rate", "-of", "default=noprint_wrappers=1:nokey=1", m4aFileMono)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("error running ffprobe: %v", err)
}
// Parse the output to extract bit depth and sampling rate
lines := strings.Split(string(output), "\n")
// bitDepth, _ := strconv.Atoi(strings.TrimSpace(lines[1]))
sampleRate, _ := strconv.Atoi(strings.TrimSpace(lines[0]))
audioInfo := shazam.AudioInfo{
SongName: songName,
SongArtist: songArtist,
BitDepth: 2,
Channels: 1,
SamplingRate: sampleRate,
}
fmt.Println("AUDIO INFO: ", audioInfo)
// Calculate fingerprints
chunks := shazam.Chunkify(audioBytes)
_, fingerprints := shazam.FingerprintChunks(chunks, &audioInfo)
// Save fingerprints to MongoDB
for fgp, chunkData := range fingerprints {
err := db.InsertChunkData(fgp, chunkData)
if err != nil {
return fmt.Errorf("error inserting document: %v", err)
}
}
// Save the song as processed
err = db.RegisterSong(songKey)
if err != nil {
return err
}
fmt.Println("Fingerprints saved to MongoDB successfully")
return nil
}

265
spotify/spotify.go Normal file
View file

@ -0,0 +1,265 @@
package spotify
import (
"errors"
"fmt"
"io"
"math"
"net/http"
"regexp"
"strings"
"time"
"github.com/tidwall/gjson"
)
/* for playlists and albums */
type ResourceEndpoint struct {
Limit, Offset, TotalCount, Requests int64
}
type Track struct {
Title, Artist, Album string
}
const (
tokenEndpoint = "https://open.spotify.com/get_access_token?reason=transport&productType=web-player"
trackInitialPath = "https://api-partner.spotify.com/pathfinder/v1/query?operationName=getTrack&variables="
playlistInitialPath = "https://api-partner.spotify.com/pathfinder/v1/query?operationName=fetchPlaylist&variables="
albumInitialPath = "https://api-partner.spotify.com/pathfinder/v1/query?operationName=getAlbum&variables="
trackEndPath = `{"persistedQuery":{"version":1,"sha256Hash":"e101aead6d78faa11d75bec5e36385a07b2f1c4a0420932d374d89ee17c70dd6"}}`
playlistEndPath = `{"persistedQuery":{"version":1,"sha256Hash":"b39f62e9b566aa849b1780927de1450f47e02c54abf1e66e513f96e849591e41"}}`
albumEndPath = `{"persistedQuery":{"version":1,"sha256Hash":"46ae954ef2d2fe7732b4b2b4022157b2e18b7ea84f70591ceb164e4de1b5d5d3"}}`
)
func accessToken() (string, error) {
resp, err := http.Get(tokenEndpoint)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
accessToken := gjson.Get(string(body), "accessToken")
return accessToken.String(), nil
}
/* requests to playlist/track endpoints */
func request(endpoint string) (int, string, error) {
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return 0, "", fmt.Errorf("error on making the request")
}
bearer, err := accessToken()
if err != nil {
return 0, "", fmt.Errorf("failed to get access token: %w", err)
}
req.Header.Add("Authorization", "Bearer "+bearer)
resp, err := (&http.Client{}).Do(req)
if err != nil {
return 0, "", fmt.Errorf("error on getting response: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return 0, "", fmt.Errorf("error on reading response: %w", err)
}
return resp.StatusCode, string(body), nil
}
func getID(url string) string {
parts := strings.Split(url, "/")
id := strings.Split(parts[4], "?")[0]
return id
}
func isValidPattern(url, pattern string) bool {
match, _ := regexp.MatchString(pattern, url)
return match
}
func TrackInfo(url string) (*Track, error) {
trackPattern := `^https:\/\/open\.spotify\.com\/track\/[a-zA-Z0-9]{22}\?si=[a-zA-Z0-9]{16}$`
if !isValidPattern(url, trackPattern) {
return nil, errors.New("invalid track url")
}
id := getID(url)
endpointQuery := EncodeParam(fmt.Sprintf(`{"uri":"spotify:track:%s"}`, id))
endpoint := trackInitialPath + endpointQuery + "&extensions=" + EncodeParam(trackEndPath)
statusCode, jsonResponse, err := request(endpoint)
// fmt.Print("TRACK INFO: ", jsonResponse)
if err != nil {
return nil, fmt.Errorf("error on getting track info: %w", err)
}
if statusCode != 200 {
return nil, fmt.Errorf("received non-200 status code: %d", statusCode)
}
track := &Track{
Title: gjson.Get(jsonResponse, "data.trackUnion.name").String(),
Artist: gjson.Get(jsonResponse, "data.trackUnion.firstArtist.items.0.profile.name").String(),
Album: gjson.Get(jsonResponse, "data.trackUnion.albumOfTrack.name").String(),
}
var allArtists []string
if firstArtist := gjson.Get(jsonResponse, "data.trackUnion.firstArtist.items.0.profile.name").String(); firstArtist != "" {
allArtists = append(allArtists, firstArtist)
}
if artists := gjson.Get(jsonResponse, "data.trackUnion.otherArtists.items").Array(); len(artists) > 0 {
for _, artist := range artists {
if profile := artist.Get("profile").Map(); len(profile) > 0 {
if name := profile["name"].String(); name != "" {
allArtists = append(allArtists, name)
}
}
}
}
fmt.Println("ARTISTS: ", allArtists)
fmt.Println("TRACK: ", track)
return track.buildTrack(), nil
}
func PlaylistInfo(url string) ([]Track, error) {
playlistPattern := `^https:\/\/open\.spotify\.com\/playlist\/[a-zA-Z0-9]{22}\?si=[a-zA-Z0-9]{16}$`
if !isValidPattern(url, playlistPattern) {
return nil, errors.New("invalid playlist url")
}
totalCount := "data.playlistV2.content.totalCount"
itemsArray := "data.playlistV2.content.items"
tracks, err := resourceInfo(url, "playlist", totalCount, itemsArray)
if err != nil {
return nil, err
}
return tracks, nil
}
func AlbumInfo(url string) ([]Track, error) {
albumPattern := `^https:\/\/open\.spotify\.com\/album\/[a-zA-Z0-9-]{22}\?si=[a-zA-Z0-9_-]{22}$`
if !isValidPattern(url, albumPattern) {
return nil, errors.New("invalid album url")
}
totalCount := "data.albumUnion.discs.items.0.tracks.totalCount"
itemsArray := "data.albumUnion.discs.items"
tracks, err := resourceInfo(url, "album", totalCount, itemsArray)
if err != nil {
return nil, err
}
return tracks, nil
}
/* returns playlist/album slice of tracks */
func resourceInfo(url, resourceType, totalCount, itemList string) ([]Track, error) {
id := getID(url)
eConf := ResourceEndpoint{Limit: 400, Offset: 0}
jsonResponse, err := jsonList(resourceType, id, eConf.Offset, eConf.Limit)
if err != nil {
return nil, err
}
eConf.TotalCount = gjson.Get(jsonResponse, totalCount).Int()
if eConf.TotalCount < 1 {
return nil, errors.New("hum, there are no tracks")
}
name := map[bool]string{true: gjson.Get(jsonResponse, "data.playlistV2.name").String(), false: gjson.Get(jsonResponse, "data.albumUnion.name").String()}[resourceType == "playlist"]
fmt.Printf("Collecting tracks from '%s'...\n", name)
time.Sleep(1 * time.Second)
eConf.Requests = int64(math.Ceil(float64(eConf.TotalCount) / float64(eConf.Limit))) /* total of requests */
var tracks []Track
tracks = append(tracks, proccessItems(jsonResponse, resourceType)...)
for i := 1; i < int(eConf.Requests); i++ {
eConf.pagination()
jsonResponse, err := jsonList(resourceType, id, eConf.Offset, eConf.Limit)
if err != nil {
return nil, err
}
tracks = append(tracks, proccessItems(jsonResponse, resourceType)...)
}
fmt.Println("Tracks collected:", len(tracks))
return tracks, nil
}
/* gets JSON respond from playlist/album endpoints */
func jsonList(resourceType, id string, offset, limit int64) (string, error) {
var endpointQuery string
var endpoint string
if resourceType == "playlist" {
endpointQuery = EncodeParam(fmt.Sprintf(`{"uri":"spotify:playlist:%s","offset":%d,"limit":%d}`, id, offset, limit))
endpoint = playlistInitialPath + endpointQuery + "&extensions=" + EncodeParam(playlistEndPath)
} else {
endpointQuery = EncodeParam(fmt.Sprintf(`{"uri":"spotify:album:%s","locale":"","offset":%d,"limit":%d}`, id, offset, limit))
endpoint = albumInitialPath + endpointQuery + "&extensions=" + EncodeParam(albumEndPath)
}
statusCode, jsonResponse, err := request(endpoint)
if err != nil {
return "", fmt.Errorf("error getting tracks: %w", err)
}
if statusCode != 200 {
return "", fmt.Errorf("received non-200 status code: %d", statusCode)
}
return jsonResponse, nil
}
func (t *Track) buildTrack() *Track {
track := &Track{
Title: t.Title,
Artist: t.Artist,
Album: t.Album,
}
return track
}
func (eConf *ResourceEndpoint) pagination() {
eConf.Offset = eConf.Offset + eConf.Limit
}
/* constructs each Spotify track from JSON body (album/playlist) and returns a slice of tracks */
func proccessItems(jsonResponse, resourceType string) []Track {
itemList := map[bool]string{true: "data.playlistV2.content.items", false: "data.albumUnion.tracks.items"}[resourceType == "playlist"]
songTitle := map[bool]string{true: "itemV2.data.name", false: "track.name"}[resourceType == "playlist"]
artistName := map[bool]string{true: "itemV2.data.artists.items.0.profile.name", false: "track.artists.items.0.profile.name"}[resourceType == "playlist"]
albumName := map[bool]string{true: "itemV2.data.albumOfTrack.name", false: "data.albumUnion.name"}[resourceType == "playlist"]
var tracks []Track
items := gjson.Get(jsonResponse, itemList).Array()
for _, item := range items {
track := &Track{
Title: item.Get(songTitle).String(),
Artist: item.Get(artistName).String(),
Album: map[bool]string{true: item.Get(albumName).String(), false: gjson.Get(jsonResponse, albumName).String()}[resourceType == "playlist"],
}
tracks = append(tracks, *track.buildTrack())
}
return tracks
}

244
spotify/utils.go Normal file
View file

@ -0,0 +1,244 @@
package spotify
import (
"archive/zip"
"errors"
"fmt"
"io"
"io/ioutil"
"net"
"net/url"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"unicode"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
)
func EncodeParam(s string) string {
return url.QueryEscape(s)
}
func ToLowerCase(s string) string {
var result string
for _, char := range s {
if char >= 'A' && char <= 'Z' {
result += string(char + 32)
} else {
result += string(char)
}
}
return result
}
func isPathValid(path string) bool {
dir, err := os.Stat(path)
if err != nil {
fmt.Println(err)
return false
}
if !dir.IsDir() {
return false
}
return true
}
func NewDir(path string) (string, error) {
if !isPathValid(path) {
return "", errors.New("invalid path")
}
dirName := "YourMusic"
fullPath := filepath.Join(path, dirName)
if runtime.GOOS == "windows" {
fullPath = fullPath + "\\"
} else {
fullPath = fullPath + "/"
}
err := os.Mkdir(fullPath, 0700)
if err != nil {
fmt.Sprintln("Error: %w", err)
return "", err
}
return fullPath, nil
}
func ToZip(dir, zipPath string) error {
zipFile, err := os.Create(zipPath)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
err = filepath.Walk(dir, func(filePath string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if filePath == dir {
return nil
}
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name, _ = filepath.Rel(dir, filePath)
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(writer, file)
if err != nil {
return err
}
}
return nil
})
return err
}
func GetFileSize(file string) (int64, error) {
fileInfo, err := os.Stat(file)
if err != nil {
return 0, err
}
size := int64(fileInfo.Size())
return size, nil
}
func RemoveAccents(s string) string {
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
output, _, e := transform.String(t, s)
if e != nil {
panic(e)
}
return output
}
func RemoveInvalidChars(input string, invalidChars []byte) string {
filter := func(r rune) rune {
for _, c := range invalidChars {
if byte(r) == c {
return -1 /* remove the char */
}
}
return r
}
return strings.Map(filter, input)
}
func GetLocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return ""
}
/* catches interrupt signal (ctrl+c) */
func SetupCloseHandler(tempDir, zipFile string) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
DeleteFile(tempDir)
DeleteFile(zipFile)
os.Exit(0)
}()
}
func DeleteFile(filePath string) {
if _, err := os.Stat(filePath); err == nil {
if err := os.RemoveAll(filePath); err != nil {
fmt.Println("Error deleting file:", err)
}
}
}
/* used for the last validation in the Match function */
func ExtractFirstWord(value string) string {
for i := range value {
if value[i] == ' ' {
return value[0:i]
}
}
return value
}
/*
i don't know why, but there are artists who,
due to their name, they add a hyphen
between some words of their names
on one platform and not on the other
*/
func CleanAndNormalize(s string) string {
cleaned := strings.ReplaceAll(s, "-", "")
cleaned = strings.ReplaceAll(cleaned, " ", "")
return cleaned
}
// Convert M4A file from stereo to mono
func ConvertM4aToMono(inputFile, outputFile string) ([]byte, error) {
cmd := exec.Command("ffprobe", "-v", "error", "-show_entries", "stream=channels", "-of", "default=noprint_wrappers=1:nokey=1", inputFile)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, fmt.Errorf("error running ffprobe: %v, %v", err, string(output))
}
audioBytes, err := ioutil.ReadFile(inputFile)
if err != nil {
return nil, fmt.Errorf("error reading input file: %v", err)
}
channels := strings.TrimSpace(string(output))
if channels != "1" {
cmd = exec.Command("ffmpeg", "-i", inputFile, "-af", "pan=mono|c0=c0", outputFile)
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("error running ffmpeg: %v", err)
}
audioBytes, err = ioutil.ReadFile(outputFile)
if err != nil {
return nil, fmt.Errorf("error reading input file: %v", err)
}
}
return audioBytes, nil
}

39
spotify/youtube.go Normal file
View file

@ -0,0 +1,39 @@
package spotify
import (
"context"
"fmt"
"log"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
const developerKey = "AIzaSyC3nBFKqudeMItXnYKEeOUryLKhXnqBL7M"
// https://github.com/BharatKalluri/spotifydl/blob/v0.1.0/src/youtube.go
func VideoID(spTrack Track) (string, error) {
service, err := youtube.NewService(context.TODO(), option.WithAPIKey(developerKey))
if err != nil {
log.Fatalf("Error creating new YouTube client: %v", err)
return "", err
}
// Video category ID 10 is for music videos
query := fmt.Sprintf("'%s' %s %s", spTrack.Title, spTrack.Artist, spTrack.Album) /* example: 'Lovesong' The Cure Disintegration */
call := service.Search.List([]string{"id", "snippet"}).Q(query).VideoCategoryId("10").Type("video")
response, err := call.Do()
if err != nil {
log.Fatalf("Error making search API call: %v", err)
return "", err
}
for _, item := range response.Items {
switch item.Id.Kind {
case "youtube#video":
return item.Id.VideoId, nil
}
}
// TODO: Handle when the query returns no songs (highly unlikely since the query is coming from spotify though)
return "", nil
}

189
utils/dbClient.go Normal file
View file

@ -0,0 +1,189 @@
package utils
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const dbUri string = "mongodb://localhost:27017"
// DbClient represents a MongoDB client
type DbClient struct {
client *mongo.Client
}
// NewDbClient creates a new instance of DbClient
func NewDbClient() (*DbClient, error) {
clientOptions := options.Client().ApplyURI(dbUri)
client, err := mongo.Connect(context.Background(), clientOptions)
if err != nil {
return nil, err
}
return &DbClient{client: client}, nil
}
// Close closes the underlying MongoDB client
func (db *DbClient) Close() error {
if db.client != nil {
return db.client.Disconnect(context.Background())
}
return nil
}
func (db *DbClient) SongExists(key string) (bool, error) {
existingSongsCollection := db.client.Database("song-recognition").Collection("existing-songs")
filter := bson.M{"_id": key}
var result bson.M
if err := existingSongsCollection.FindOne(context.Background(), filter).Decode(&result); err == nil {
return true, nil
} else if err != mongo.ErrNoDocuments {
return false, fmt.Errorf("error querying registered songs: %v", err)
}
return false, nil
}
func (db *DbClient) RegisterSong(key string) error {
existingSongsCollection := db.client.Database("song-recognition").Collection("existing-songs")
_, err := existingSongsCollection.InsertOne(context.Background(), bson.M{"_id": key})
if err != nil {
return fmt.Errorf("error registering song: %v", err)
}
return nil
}
func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error {
chunksCollection := db.client.Database("song-recognition").Collection("chunks")
filter := bson.M{"fingerprint": chunkfgp}
var result bson.M
err := chunksCollection.FindOne(context.Background(), filter).Decode(&result)
if err == nil {
// If the fingerprint already exists, append the chunkData to the existing list
fmt.Println("DUPLICATE FINGERPRINT: ", chunkfgp)
update := bson.M{"$push": bson.M{"chunkData": chunkData}}
_, err := chunksCollection.UpdateOne(context.Background(), filter, update)
if err != nil {
return fmt.Errorf("error updating chunk data: %v", err)
}
return nil
} else if err != mongo.ErrNoDocuments {
return err
}
// If the document doesn't exist, insert a new document
_, err = chunksCollection.InsertOne(context.Background(), bson.M{"fingerprint": chunkfgp, "chunkData": []interface{}{chunkData}})
if err != nil {
return fmt.Errorf("error inserting chunk data: %v", err)
}
return nil
}
// func (db *DbClient) GetChunkData(chunkfgp int64) ([]interface{}, error) {
// chunksCollection := db.client.Database("song-recognition").Collection("chunks")
// type chunkData struct {
// ChunkData []interface{} `bson:"chunkData"`
// }
// var result chunkData
// filter := bson.M{"fingerprint": chunkfgp}
// err := chunksCollection.FindOne(context.Background(), filter).Decode(&result)
// if err != nil {
// if err == mongo.ErrNoDocuments {
// return nil, nil
// }
// return nil, fmt.Errorf("error retrieving chunk data: %w", err)
// }
// return result.ChunkData, nil
// }
type chunkData struct {
SongName string `bson:"songName"`
SongArtist string `bson:"songArtist"`
BitDepth int `bson:"bitDepth"`
Channels int `bson:"channels"`
SamplingRate int `bson:"samplingRate"`
TimeStamp string `bson:"timeStamp"`
}
// func (db *DbClient) GetChunkData(chunkfgp int64) ([]chunkData, error) {
// chunksCollection := db.client.Database("song-recognition").Collection("chunks")
// var result []chunkData
// filter := bson.M{"fingerprint": chunkfgp}
// err := chunksCollection.FindOne(context.Background(), filter).Decode(&result)
// if err != nil {
// if err == mongo.ErrNoDocuments {
// return nil, nil
// }
// return nil, fmt.Errorf("error retrieving chunk data: %w", err)
// }
// return result, nil
// }
// func (db *DbClient) GetChunkData(chunkfgp int64) ([]map[string]interface{}, error) {
// chunksCollection := db.client.Database("song-recognition").Collection("chunks")
// // Change FindOne to Find to retrieve multiple documents
// filter := bson.M{"fingerprint": chunkfgp}
// cursor, err := chunksCollection.Find(context.Background(), filter)
// if err != nil {
// return nil, fmt.Errorf("error retrieving chunk data: %w", err)
// }
// defer cursor.Close(context.Background())
// var results []map[string]interface{}
// for cursor.Next(context.Background()) {
// var data map[string]interface{} // Assuming retrieved data is a map
// if err := cursor.Decode(&data); err != nil {
// return nil, fmt.Errorf("error decoding chunk data: %w", err)
// }
// // Append original map to the results slice
// results = append(results, data)
// }
// if err := cursor.Err(); err != nil {
// return nil, fmt.Errorf("error iterating through cursor: %w", err)
// }
// return results, nil
// }
func (db *DbClient) GetChunkData(chunkfgp int64) ([]primitive.M, error) {
chunksCollection := db.client.Database("song-recognition").Collection("chunks")
filter := bson.M{"fingerprint": chunkfgp}
result := bson.M{}
err := chunksCollection.FindOne(context.Background(), filter).Decode(&result)
if err != nil {
if err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, fmt.Errorf("error retrieving chunk data: %w", err)
}
var listOfChunkData []primitive.M
for _, data := range result["chunkData"].(primitive.A) {
listOfChunkData = append(listOfChunkData, data.(primitive.M))
}
return listOfChunkData, nil
}