mirror of
https://github.com/cgzirim/seek-tune.git
synced 2025-12-17 08:54:19 +00:00
Initial commit.
This commit is contained in:
parent
2873cbe948
commit
1689cc42b5
15 changed files with 2396 additions and 0 deletions
84
go.mod
Normal file
84
go.mod
Normal 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
386
go.sum
Normal 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
64
imain.go
Normal 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
277
main.go
Normal 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
40
shazam/fft.go
Normal 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
264
shazam/shazam.go
Normal 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
0
signal/base64.txt
Normal file
31
signal/http.go
Normal file
31
signal/http.go
Normal 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
19
signal/rand.go
Normal 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
121
signal/signal.go
Normal 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
373
spotify/downloader.go
Normal 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
265
spotify/spotify.go
Normal 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
244
spotify/utils.go
Normal 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
39
spotify/youtube.go
Normal 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
189
utils/dbClient.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue