Saving to github after many changes

This commit is contained in:
Chigozirim Igweamaka 2024-03-26 10:00:34 +01:00
parent 8d1742fc2b
commit a1ba649480
31 changed files with 25974 additions and 386 deletions

1
.gitignore vendored
View file

@ -19,3 +19,4 @@
# Go workspace file
go.work
/songs

14
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff",
// "editor.codeActionsOnSave": {
// "source.organizeImports": "explicit"
// },
},
"[javascript]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"python.formatting.provider": "none"
}

23
client/.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

70
client/README.md Normal file
View file

@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)

23412
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

43
client/package.json Normal file
View file

@ -0,0 +1,43 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "4.0.3",
"simple-peer": "^9.11.1",
"socket.io-client": "^2.5.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"react-error-overlay": "^6.0.9"
}
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

43
client/public/index.html Normal file
View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

BIN
client/public/logo192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
client/public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

3
client/public/robots.txt Normal file
View file

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

168
client/src/App.js Normal file
View file

@ -0,0 +1,168 @@
import React, { useEffect, useState } from "react";
import Peer from "simple-peer";
import io, { managers } from "socket.io-client";
import Form from "./Form";
// const socket = io.connect('http://localhost:5000/');
var socket = io("http://localhost:5000/");
function App() {
const [offer, setOffer] = useState();
const [stream, setStream] = useState();
const [matches, setMatches] = useState([]);
const [serverEngaged, setServerEngaged] = useState(false);
const [peerConnection, setPeerConnection] = useState();
// Function to initiate the client peer
function initiateClientPeer(stream = null) {
const peer = new Peer({
initiator: true,
trickle: false,
stream: stream,
});
let offerHasBeenSet = false;
peer.on("signal", (data) => {
if (!offerHasBeenSet) {
console.log("Setting Offer!");
setOffer(JSON.stringify(data));
offerHasBeenSet = true;
}
});
peer.on("close", () => {
console.log("CONNECTION CLOSED");
});
peer.on("error", (err) => {
console.error("An error occurred:", err);
});
setPeerConnection(peer);
}
useEffect(() => {
console.log("Offer updated:", offer);
let renegotiated = false;
if (offer && stream && !renegotiated) {
let offerEncoded = btoa(offer);
socket.emit("engage", offerEncoded);
socket.on("serverEngaged", (answer) => {
console.log("ServerSDP: ", answer);
let decodedAnswer = atob(answer);
peerConnection.signal(decodedAnswer);
console.log("Engaging Server");
setServerEngaged(true);
renegotiated = true;
});
}
}, [offer]);
useEffect(() => {
initiateClientPeer();
}, []);
// socket.on("connect", () => {
// initiateClientPeer();
// });
socket.on("matches", (matches) => {
matches = JSON.parse(matches);
setMatches(matches);
console.log("Matches: ", matches);
});
socket.on("downloadStatus", (msg) => {
console.log("downloadStatus: ", msg);
});
socket.on("albumStat", (msg) => {
console.log("Album stat: ", msg);
});
socket.on("playlistStat", (msg) => {
console.log("Playlist stat: ", msg);
});
const streamAudio = () => {
navigator.mediaDevices
.getDisplayMedia({ audio: true })
.then((stream) => {
peerConnection.addStream(stream);
// Renegotiate
let initOfferEncoded = btoa(offer);
socket.emit("initOffer", initOfferEncoded);
socket.on("initAnswer", (answer) => {
let decodedAnswer = atob(answer);
peerConnection.signal(decodedAnswer);
console.log("Renogotiated");
});
// End of Renegotiation
peerConnection.on("signal", (data) => {
setOffer(JSON.stringify(data));
console.log("Offer should be reset");
});
setStream(stream); // Set the audio stream to state
})
.catch((error) => {
console.error("Error accessing user media:", error);
// Handle error
});
if (!offer || !peerConnection) {
// If offer is not set, create a new one
console.log("NO OFFER. CREATING OFFER");
initiateClientPeer(stream);
}
};
const disengageServer = () => {
peerConnection.destroy();
};
return (
<div className="App">
<h1>New App</h1>
<div>
{serverEngaged ? (
<button disabled={true}>Listening</button>
) : (
<button onClick={() => streamAudio()}>Listen</button>
)}
{serverEngaged && (
<button onClick={() => disengageServer()}>Stop Listening</button>
)}
</div>
<Form socket={socket} />
<div>
{matches.map((match, index) => {
const [h, m, s] = match.timestamp.split(":");
const timestamp =
parseInt(h, 10) * 120 + parseInt(m, 10) * 60 + parseInt(s, 10);
return (
<iframe
key={index}
width="460"
height="284"
src={`https://www.youtube.com/embed/${match.youtubeid}?start=${timestamp}`}
title={match.songname}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>
);
})}
</div>
</div>
);
}
export default App;

80
client/src/Form.js Normal file
View file

@ -0,0 +1,80 @@
import React, { Component } from "react";
class Form extends Component {
initialState = { spotifyUrl: "" };
state = this.initialState;
handleChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
};
submitForm = () => {
const { spotifyUrl } = this.state;
const { socket } = this.props;
if (this.spotifyURLisValid(spotifyUrl) === false) {
return;
}
socket.emit("newDownload", spotifyUrl);
console.log("newDownload: ", spotifyUrl);
this.setState(this.initialState);
};
spotifyURLisValid = (url) => {
if (url.length === 0) {
console.log("Spotify URL required");
return false;
}
const splitURL = url.split("/");
if (splitURL.length < 2) {
console.log("Invalid Spotify URL format");
return false;
}
let spotifyID = splitURL[splitURL.length - 1];
if (spotifyID.includes("?")) {
spotifyID = spotifyID.split("?")[0];
}
// Check if the Spotify ID is alphanumeric
if (!/^[a-zA-Z0-9]+$/.test(spotifyID)) {
console.log("Invalid Spotify ID format");
return false;
}
// Check if the Spotify ID is of expected length
if (spotifyID.length !== 22) {
console.log("Invalid Spotify ID length");
return false;
}
// Additional validation logic can be added here
return true;
};
render() {
const { spotifyUrl } = this.state;
return (
<form>
<label htmlFor="spotifyUrl">spotifyUrl</label>
<input
type="text"
name="spotifyUrl"
id="spotifyUrl"
value={spotifyUrl}
placeholder="https://open.spotify.com/.../..."
onChange={this.handleChange}
/>
<input type="button" value="Submit" onClick={this.submitForm} />
</form>
);
}
}
export default Form;

42
client/src/Table.js Normal file
View file

@ -0,0 +1,42 @@
function TableHeader () {
return (
<thead>
<tr>
<th>Name</th>
<th>Job</th>
<th>Remove</th>
</tr>
</thead>
)
}
function TableBody (props) {
const rows = props.characterData.map((row, index) => {
return (
<tr key={index}>
<td>{row.name}</td>
<td>{row.job}</td>
<td>
<button onClick={() => props.removeCharacter(index)}>Delete</button>
</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
function Table (props) {
const { characterData, removeCharacter } = props
return (
<table>
<TableHeader />
<TableBody characterData={characterData} removeCharacter={removeCharacter} />
</table>
)
}
export default Table

1030
client/src/index.css Normal file

File diff suppressed because one or more lines are too long

17
client/src/index.js Normal file
View file

@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View file

@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

50
go.mod
View file

@ -5,13 +5,21 @@ 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/BharatKalluri/spotifydl v0.1.0 // indirect
github.com/adrg/strutil v0.3.1 // indirect
github.com/bitly/go-simplejson v0.5.1 // indirect
github.com/bogem/id3v2 v1.1.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // 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/dlclark/regexp2 v1.11.0 // indirect
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // 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
@ -20,21 +28,37 @@ require (
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/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.4+incompatible // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gofrs/uuid v4.0.0+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/gomodule/redigo v1.8.4 // indirect
github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07 // 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/googollee/go-socket.io v1.7.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/hajimehoshi/go-mp3 v0.3.4 // indirect
github.com/kkdai/youtube/v2 v2.10.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kkdai/youtube/v2 v2.10.1 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.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
@ -54,26 +78,32 @@ require (
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/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // 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/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // 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
github.com/zmb3/spotify v0.0.0-20191028153142-869e03dbd8b0 // 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/arch v0.3.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/net v0.22.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/sys v0.18.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

156
go.sum
View file

@ -1,21 +1,41 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
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/BharatKalluri/spotifydl v0.1.0 h1:BzaukOeFenfmSFFSfHPUIF839+lIPr7G8MRaw0Q1b2Q=
github.com/BharatKalluri/spotifydl v0.1.0/go.mod h1:NBlYj+lhmo/TaL4w1c1nsVcU2/prqcqvKYrrsEUYcjA=
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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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/bogem/id3v2 v1.1.1 h1:FnjS2vytMeEb39tOMG09uz852MaEccA2A3asRM3XxbE=
github.com/bogem/id3v2 v1.1.1/go.mod h1:D1rDm80qF/ocBU+Ik8U4RKnwMq/oNkkB8vGcnrlMJmM=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
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/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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=
@ -26,9 +46,13 @@ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwu
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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.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 v0.0.0-20240220182346-e401ed450204 h1:O7I1iuzEA7SG+dK8ocOBSlYAA9jBUmCYl/Qa7ey7JAM=
github.com/dop251/goja v0.0.0-20240220182346-e401ed450204/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=
@ -41,6 +65,12 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
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/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
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=
@ -58,15 +88,29 @@ 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-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
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-sourcemap/sourcemap v2.1.4+incompatible h1:a+iTbH5auLKxaNwQFg0B+TCYl6lbukKPc7b5x0n1s6Q=
github.com/go-sourcemap/sourcemap v2.1.4+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/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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/mock v1.2.0/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.1/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=
@ -82,6 +126,9 @@ 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/gomodule/redigo v1.8.4 h1:Z5JUg94HMTR1XpwBaSH4vq3+PNSIykBLxMdglbw10gg=
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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=
@ -89,9 +136,14 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
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/pprof v0.0.0-20240320155624-b11c3daa6f07 h1:57oOH2Mu5Nw16KnZAVLdlUjmPH/TSYCKTJgG0OVfX0Y=
github.com/google/pprof v0.0.0-20240320155624-b11c3daa6f07/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
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=
@ -99,30 +151,62 @@ 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.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
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/googollee/go-socket.io v1.7.0 h1:ODcQSAvVIPvKozXtUGuJDV3pLwdpBLDs1Uoq/QHIlY8=
github.com/googollee/go-socket.io v1.7.0/go.mod h1:0vGP8/dXR9SZUMMD4+xxaGo/lohOw3YWMh2WRiWeKxg=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
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/kkdai/youtube/v2 v2.10.1 h1:jdPho4R7VxWoRi9Wx4ULMq4+hlzSVOXxh4Zh83f2F9M=
github.com/kkdai/youtube/v2 v2.10.1/go.mod h1:qL8JZv7Q1IoDs4nnaL51o/hmITXEIvyCIXopB0oqgVM=
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
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/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk=
github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12/go.mod h1:i/KKcxEWEO8Yyl11DYafRPKOPVYTrhxiTRigjtEEXZU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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=
@ -134,6 +218,12 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
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/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
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=
@ -184,36 +274,65 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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.2/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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
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=
github.com/zmb3/spotify v0.0.0-20191028153142-869e03dbd8b0 h1:MVLklg1SWVS2rvK1NDXmy04rgjfq7dCnyncqZfPWL+A=
github.com/zmb3/spotify v0.0.0-20191028153142-869e03dbd8b0/go.mod h1:pHsWAmY9PfX7i/uwPZkmWrebc8JbK8FppKbvyevwzSU=
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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
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=
@ -224,6 +343,10 @@ go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV
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/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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=
@ -235,19 +358,25 @@ 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/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/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-20190108225652-1e06a53dbb7e/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-20190503192946-f4e77d36d62c/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=
@ -264,11 +393,18 @@ 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/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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=
@ -277,8 +413,10 @@ 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-20181205085412-a5c9d58dba9a/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-20190507160741-ecd444e8653b/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=
@ -290,6 +428,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
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-20220704084225-05e143d24a9e/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=
@ -304,6 +443,8 @@ 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/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.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=
@ -313,6 +454,8 @@ 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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
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=
@ -323,10 +466,13 @@ 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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
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=
@ -336,19 +482,26 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
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.5.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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
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=
@ -383,4 +536,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View file

@ -34,28 +34,39 @@ func matchSong(songPath string) error {
return nil
}
func imain() {
func main() {
// 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/6h2vZPWSWsRJ0ps91epUgT?si=7ac5c26041014ea4 - What's going on
// 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
// https://open.spotify.com/track/3ddxe0WYUpNPtSnHgQOad5?si=8c1665c5b1384e9e - I still have faith in you
// 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",
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/6h2vZPWSWsRJ0ps91epUgT?si=7ac5c26041014ea4",
"/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.DlSingleTrack("https://open.spotify.com/track/3ddxe0WYUpNPtSnHgQOad5?si=8c1665c5b1384e9e",
"/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/")
// AJR Mix
// spotify.DlPlaylist("https://open.spotify.com/playlist/37i9dQZF1EIZjJcbmXVBoA?si=35d7d4ba237147cf",
// "/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)

42
main.go
View file

@ -22,7 +22,6 @@ import (
"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"
)
@ -69,7 +68,7 @@ func saveToBytes(track *webrtc.TrackRemote) ([]byte, error) {
return audioData, nil
}
func matchSampleAudio(track *webrtc.TrackRemote) (string, error) {
func MatchSampleAudio(track *webrtc.TrackRemote) (string, error) {
// Use time.After to stop after 15 seconds
stop := time.After(50 * time.Second)
@ -127,13 +126,7 @@ func main() {
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)
}
// We'll use Opus, but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 44100, Channels: 1, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 111,
@ -142,15 +135,11 @@ func main() {
}
// 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.
// This provides NACKs, RTCP Reports and other features.
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
// This interceptor sends a PLI every 3 seconds. A PLI causes a keyframe to be generated by the sender.
intervalPliFactory, err := intervalpli.NewReceiverInterceptor()
if err != nil {
panic(err)
@ -180,35 +169,24 @@ func main() {
panic(err)
}
// Allow us to receive 1 audio track, and 1 video track
// Allow us to receive 1 audio 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)
// Create an Ogg file for audio output
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
// an Ogg file.
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)
fmt.Println("Got Opus track, saving to disk as output.opus (44.1 kHz, 1 channel)")
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)
}
})
@ -224,10 +202,6 @@ func main() {
panic(closeErr)
}
if closeErr := ivfFile.Close(); closeErr != nil {
panic(closeErr)
}
fmt.Println("Done writing media files")
// Gracefully shutdown the peer connection

224
server.go Normal file
View file

@ -0,0 +1,224 @@
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"song-recognition/signal"
"song-recognition/spotify"
"strings"
"github.com/gin-gonic/gin"
"github.com/pion/webrtc/v4"
"github.com/pion/webrtc/v4/pkg/media/oggwriter"
socketio "github.com/googollee/go-socket.io"
)
const (
tmpSongDir = "/home/chigozirim/Documents/my-docs/song-recognition/songs/"
)
func GinMiddleware(allowOrigin string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", allowOrigin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Request.Header.Del("Origin")
c.Next()
}
}
func main() {
router := gin.New()
server := socketio.NewServer(nil)
server.OnConnect("/", func(s socketio.Conn) error {
s.SetContext("")
log.Println("CONNECTED: ", s.ID())
return nil
})
server.OnEvent("/", "initOffer", func(s socketio.Conn, initEncodedOffer string) {
log.Println("initOffer: ", initEncodedOffer)
peerConnection := signal.SetupWebRTC(initEncodedOffer)
s.Emit("initAnswer", signal.Encode(*peerConnection.LocalDescription()))
})
server.OnEvent("/", "engage", func(s socketio.Conn, encodedOffer string) {
log.Println("engage: ", encodedOffer)
peerConnection := signal.SetupWebRTC(encodedOffer)
// Allow us to receive 1 audio track
if _, err := peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
}
// Set a handler for when a new remote track starts, this handler saves buffers to disk as
// an Ogg file.
oggFile, err := oggwriter.New("output.ogg", 44100, 1)
if err != nil {
panic(err)
}
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 (44.1 kHz, 1 channel)")
// signal.SaveToDisk(oggFile, track)
// TODO turn match to json here
matches, err := signal.MatchSampleAudio(track)
if err != nil {
panic(err)
}
jsonData, err := json.Marshal(matches[:5])
if err != nil {
fmt.Println("Log error: ", err)
return
}
fmt.Println(string(jsonData))
s.Emit("matches", string(jsonData))
peerConnection.Close()
}
})
// 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)
}
fmt.Println("Done writing media files")
// Gracefully shutdown the peer connection
if closeErr := peerConnection.Close(); closeErr != nil {
panic(closeErr)
}
// os.Exit(0)
}
})
// Emit answer in base64
s.Emit("serverEngaged", signal.Encode(*peerConnection.LocalDescription()))
})
server.OnEvent("/", "newDownload", func(socket socketio.Conn, spotifyURL string) {
if len(spotifyURL) == 0 {
fmt.Println("=> Spotify URL required.")
return
}
splitURL := strings.Split(spotifyURL, "/")
if len(splitURL) < 2 {
fmt.Println("=> Please enter the url copied from the spotify client.")
return
}
spotifyID := splitURL[len(splitURL)-1]
if strings.Contains(spotifyID, "?") {
spotifyID = strings.Split(spotifyID, "?")[0]
}
if strings.Contains(spotifyURL, "album") {
tracksInAlbum, err := spotify.AlbumInfo(spotifyURL)
if err != nil {
fmt.Println("log error: ", err)
return
}
socket.Emit("albumStat", fmt.Sprintf("%v songs found in album.", len(tracksInAlbum)))
totalTracksDownloaded, err := spotify.DlAlbum(spotifyURL, tmpSongDir)
if err != nil {
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download album."))
return
}
socket.Emit("downloadStatus", fmt.Sprintf("%d songs downloaded from album", totalTracksDownloaded))
} else if strings.Contains(spotifyURL, "playlist") {
tracksInPL, err := spotify.PlaylistInfo(spotifyURL)
if err != nil {
fmt.Println("log error: ", err)
return
}
socket.Emit("playlistStat", fmt.Sprintf("%v songs found in playlist.", len(tracksInPL)))
totalTracksDownloaded, err := spotify.DlPlaylist(spotifyURL, tmpSongDir)
if err != nil {
fmt.Println("log errorr: ", err)
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download playlist."))
return
}
socket.Emit("downloadStatus", fmt.Sprintf("%d songs downloaded from playlist", totalTracksDownloaded))
} else if strings.Contains(spotifyURL, "track") {
// check if track already exist
trackInfo, err := spotify.TrackInfo(spotifyURL)
if err != nil {
fmt.Println("log error: ", err)
return
}
err = spotify.DlSingleTrack(spotifyURL, tmpSongDir)
if err != nil {
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download '%s' by '%s'", trackInfo.Title, trackInfo.Artist))
return
}
socket.Emit("downloadStatus", fmt.Sprintf("'%s' by '%s' was downloaded", trackInfo.Title, trackInfo.Artist))
} else {
fmt.Println("=> Only Spotify Album/Playlist/Track URL's are supported.")
return
}
})
server.OnError("/", func(s socketio.Conn, e error) {
log.Println("meet error:", e)
})
server.OnDisconnect("/", func(s socketio.Conn, reason string) {
log.Println("closed", reason)
})
go func() {
if err := server.Serve(); err != nil {
log.Fatalf("socketio listen error: %s\n", err)
}
}()
defer server.Close()
router.Use(GinMiddleware("http://localhost:3000"))
router.GET("/socket.io/*any", gin.WrapH(server))
router.POST("/socket.io/*any", gin.WrapH(server))
if err := router.Run(":5000"); err != nil {
log.Fatal("failed run app: ", err)
}
}

View file

@ -10,71 +10,75 @@ import (
"song-recognition/utils"
"sort"
"time"
"github.com/mjibson/go-dsp/fft"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Constants
const (
chunkSize = 4096 // 4KB
hopSize = 128
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
type ChunkTag struct {
SongName string
SongArtist string
YouTubeID string
TimeStamp string
}
func Match(sampleAudio []byte) (string, error) {
func Match(sampleAudio []byte) ([]primitive.M, error) {
sampleChunks := Chunkify(sampleAudio)
chunkFingerprints, _ := FingerprintChunks(sampleChunks, nil)
db, err := utils.NewDbClient()
if err != nil {
return "", fmt.Errorf("error connecting to DB: %d", err)
return nil, fmt.Errorf("error connecting to DB: %d", err)
}
defer db.Close()
var results = make(map[string][]string)
var chunkTags = make(map[string]primitive.M)
var songsTimestamps = make(map[string][]string)
for _, chunkfgp := range chunkFingerprints {
listOfChunkData, err := db.GetChunkData(chunkfgp)
listOfChunkTags, err := db.GetChunkData(chunkfgp)
if err != nil {
return "", fmt.Errorf("error getting chunk data with fingerpring %d: %v", chunkfgp, err)
return nil, fmt.Errorf("error getting chunk data with fingerprint %d: %v", chunkfgp, err)
}
for _, chunkData := range listOfChunkData {
timeStamp := fmt.Sprint(chunkData["timestamp"])
songKey := fmt.Sprintf("%s by %s", chunkData["songname"], chunkData["songartist"])
for _, chunkTag := range listOfChunkTags {
timeStamp := fmt.Sprint(chunkTag["timestamp"])
songKey := fmt.Sprintf("%s by %s", chunkTag["songname"], chunkTag["songartist"])
if results[songKey] == nil {
results[songKey] = []string{timeStamp}
if songsTimestamps[songKey] == nil {
songsTimestamps[songKey] = []string{timeStamp}
chunkTags[songKey] = chunkTag
} else {
results[songKey] = append(results[songKey], timeStamp)
songsTimestamps[songKey] = append(songsTimestamps[songKey], timeStamp)
}
}
}
fmt.Println("Results: ", results)
maxMatchCount := 0
var maxMatch string
for songKey, timestamps := range results {
matches := make(map[string][]int)
for songKey, timestamps := range songsTimestamps {
differences, err := timeDifference(timestamps)
if err != nil && err.Error() == "insufficient timestamps" {
continue
} else if err != nil {
return "", err
return nil, err
}
fmt.Printf("%s DIFFERENCES: %d\n", songKey, differences)
if len(differences) >= 2 {
matches[songKey] = differences
if len(differences) > maxMatchCount {
maxMatchCount = len(differences)
maxMatch = songKey
@ -82,8 +86,41 @@ func Match(sampleAudio []byte) (string, error) {
}
}
sortedChunkTags := sortMatchesByTimeDifference(matches, chunkTags)
fmt.Println("SORTED CHUNK TAGS: ", sortedChunkTags)
fmt.Println("MATCHES: ", matches)
fmt.Println("MATCH: ", maxMatch)
return "", nil
fmt.Println()
return sortedChunkTags, nil
}
func sortMatchesByTimeDifference(matches map[string][]int, chunkTags map[string]primitive.M) []primitive.M {
type songDifferences struct {
songKey string
differences []int
sum int
}
var kvPairs []songDifferences
for songKey, differences := range matches {
sum := 0
for _, difference := range differences {
sum += difference
}
kvPairs = append(kvPairs, songDifferences{songKey, differences, sum})
}
sort.Slice(kvPairs, func(i, j int) bool {
return kvPairs[i].sum > kvPairs[j].sum
})
var sortedChunkTags []primitive.M
for _, pair := range kvPairs {
sortedChunkTags = append(sortedChunkTags, chunkTags[pair.songKey])
}
return sortedChunkTags
}
func timeDifference(timestamps []string) ([]int, error) {
@ -105,8 +142,7 @@ func timeDifference(timestamps []string) ([]int, error) {
timestampsInSeconds[i] = (hours * 3600) + (minutes * 60) + seconds
}
sort.Ints(timestampsInSeconds)
fmt.Println("timeStampsInSeconds: ", timestampsInSeconds)
// sort.Ints(timestampsInSeconds)
differences := []int{}
@ -124,7 +160,6 @@ func timeDifference(timestamps []string) ([]int, error) {
// Chunkify divides the input audio signal into chunks and calculates the Short-Time Fourier Transform (STFT) for each chunk.
// The function returns a 2D slice containing the STFT coefficients for each chunk.
func Chunkify(audio []byte) [][]complex128 {
const hopSize = 32
numWindows := len(audio) / (chunkSize - hopSize)
chunks := make([][]complex128, numWindows)
@ -149,7 +184,8 @@ func Chunkify(audio []byte) [][]complex128 {
}
// Compute FFT
chunks[i] = Fft(chunk)
// chunks[i] = Fft(chunk)
chunks[i] = fft.FFT(chunk)
}
return chunks
@ -157,17 +193,19 @@ func Chunkify(audio []byte) [][]complex128 {
// 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) {
func FingerprintChunks(chunks [][]complex128, chunkTag *ChunkTag) ([]int64, map[int64]ChunkTag) {
var fingerprintList []int64
fingerprintMap := make(map[int64]AudioInfo)
fingerprintMap := make(map[int64]ChunkTag)
var bytesPerSecond, chunksPerSecond int
var chunksPerSecond int
var chunkCount int
var chunkTime time.Time
if audioInfo != nil {
bytesPerSecond = (samplingRate * bitDepth * channels) / 8
chunksPerSecond = bytesPerSecond / chunkSize
if chunkTag != nil {
// bytesPerSecond = (samplingRate * bitDepth * channels) / 8
chunksPerSecond = (chunkSize - hopSize) / samplingRate
chunksPerSecond = 9
fmt.Println("CHUNKS PER SECOND: ", chunksPerSecond)
// if chunkSize == 4096 {
// chunksPerSecond = 10
// }
@ -176,7 +214,7 @@ func FingerprintChunks(chunks [][]complex128, audioInfo *AudioInfo) ([]int64, ma
}
for _, chunk := range chunks {
if audioInfo != nil {
if chunkTag != nil {
chunkCount++
if chunkCount == chunksPerSecond {
chunkCount = 0
@ -218,13 +256,22 @@ func FingerprintChunks(chunks [][]complex128, audioInfo *AudioInfo) ([]int64, ma
int64(chunkMags["250-500"]),
int64(chunkMags["500-2000"]),
int64(chunkMags["2000-4000"])}
key := hash1(points[:])
// 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
// points := [6]int64{
// int64(chunkMags["20-60"]),
// int64(chunkMags["60-250"]),
// int64(chunkMags["250-500"]),
// int64(chunkMags["500-2000"]),
// int64(chunkMags["2000-4000"]),
// int64(chunkMags["4000-8000"])}
key := hash(points[:])
if chunkTag != nil {
newSampleTag := *chunkTag
newSampleTag.TimeStamp = chunkTime.Format("15:04:05")
fingerprintMap[key] = newSampleTag
} else {
fingerprintList = append(fingerprintList, key)
}
@ -234,15 +281,11 @@ func FingerprintChunks(chunks [][]complex128, audioInfo *AudioInfo) ([]int64, ma
}
func hash(values []int64) int64 {
if len(values) != 7 {
return 0 // Handle invalid input length
}
weight := 100
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
for _, value := range values {
result += (value - (value % fuzzFactor)) * int64(weight)
weight = weight * weight
}
return result

File diff suppressed because one or more lines are too long

207
signal/webrtc.go Normal file
View file

@ -0,0 +1,207 @@
// 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 signal
import (
"fmt"
"io"
"time"
"github.com/pion/interceptor"
"github.com/pion/interceptor/pkg/intervalpli"
"github.com/pion/webrtc/v4"
"go.mongodb.org/mongo-driver/bson/primitive"
"song-recognition/shazam"
"github.com/pion/webrtc/v4/pkg/media"
)
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) ([]primitive.M, 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
var matches []primitive.M
for {
select {
case <-ticker.C:
// Process sampleAudio every 2 seconds
if len(sampleAudio) > 0 {
matchess, err := shazam.Match(sampleAudio)
matches = matchess
if err != nil {
fmt.Println(err)
return nil, nil
}
// Reset sampleAudio for fresh input
// sampleAudio = nil
// if len(matches) > 0 {
// fmt.Println("FOUND A MATCH! - ", matches)
// jsonData, err := json.Marshal(matches)
// if err != nil {
// fmt.Println(err)
// return "", nil
// }
// return string(jsonData), nil
// }
}
case <-stop:
// Stop after 15 seconds
fmt.Println("Stopped after 15 seconds")
return matches, nil
default:
// Read RTP packets and accumulate sampleAudio
rtpPacket, _, err := track.ReadRTP()
if err != nil {
if err != io.EOF {
return nil, fmt.Errorf("error reading RTP packet: %d", err)
}
return nil, err
}
// Extract audio payload from RTP packet
payload := rtpPacket.Payload
sampleAudio = append(sampleAudio, payload...)
}
}
}
// nolint:gocognit
func SetupWebRTC(encodedOffer string) *webrtc.PeerConnection {
// 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 Opus, but you can also define your own
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.
i := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a keyframe to be generated by the sender.
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)
}
// Wait for the offer to be pasted
offer := webrtc.SessionDescription{}
Decode(encodedOffer, &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
return peerConnection
}

View file

@ -15,6 +15,8 @@ import (
"sync"
"time"
// "song-recognition/youtube"
"github.com/fatih/color"
"github.com/kkdai/youtube/v2"
)
@ -32,7 +34,7 @@ func DlSingleTrack(url, savePath string) error {
track := []Track{*trackInfo}
fmt.Println("Now, downloading track...")
err = dlTrack(track, savePath)
_, err = dlTrack(track, savePath)
if err != nil {
return err
}
@ -40,41 +42,42 @@ func DlSingleTrack(url, savePath string) error {
return nil
}
func DlPlaylist(url, savePath string) error {
func DlPlaylist(url, savePath string) (int, error) {
tracks, err := PlaylistInfo(url)
if err != nil {
return err
return 0, err
}
time.Sleep(1 * time.Second)
fmt.Println("Now, downloading playlist...")
err = dlTrack(tracks, savePath)
totalTracksDownloaded, err := dlTrack(tracks, savePath)
if err != nil {
fmt.Println(err)
return err
return 0, err
}
return nil
return totalTracksDownloaded, nil
}
func dlAlbum(url, savePath string) error {
func DlAlbum(url, savePath string) (int, error) {
tracks, err := AlbumInfo(url)
if err != nil {
return err
return 0, err
}
time.Sleep(1 * time.Second)
fmt.Println("Now, downloading album...")
err = dlTrack(tracks, savePath)
totalTracksDownloaded, err := dlTrack(tracks, savePath)
if err != nil {
return err
return 0, err
}
return nil
return totalTracksDownloaded, nil
}
func dlTrack(tracks []Track, path string) error {
func dlTrack(tracks []Track, path string) (int, error) {
var wg sync.WaitGroup
var downloadedTracks []string
var totalTracks int
results := make(chan int, len(tracks))
numCPUs := runtime.NumCPU()
@ -90,27 +93,30 @@ func dlTrack(tracks []Track, path string) error {
}()
trackCopy := &Track{
Title: track.Title,
Artist: track.Artist,
Album: track.Album,
Album: track.Album,
Artist: track.Artist,
Artists: track.Artists,
Duration: track.Duration,
Title: track.Title,
}
id, err := VideoID(*trackCopy)
if id == "" || err != nil {
// id1, err := VideoID(*trackCopy)
ytID, err := GetYoutubeId(*trackCopy)
if ytID == "" || 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)
err = getAudio(ytID, path, trackCopy.Title, trackCopy.Artist)
if err != nil {
yellow.Printf("Error (2): '%s' by '%s' could not be downloaded\n", trackCopy.Title, trackCopy.Artist)
yellow.Printf("Error (2): '%s' by '%s' could not be downloaded: %s\n", trackCopy.Title, trackCopy.Artist, err)
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)
err = processAndSaveSong(route, trackCopy.Title, trackCopy.Artist, ytID)
if err != nil {
yellow.Println("Error processing audio: ", err)
}
@ -129,6 +135,7 @@ func dlTrack(tracks []Track, path string) error {
}
fmt.Printf("'%s' by '%s' was downloaded\n", track.Title, track.Artist)
downloadedTracks = append(downloadedTracks, fmt.Sprintf("%s, %s", track.Title, track.Artist))
results <- 1
}(t)
}
@ -138,12 +145,12 @@ func dlTrack(tracks []Track, path string) error {
close(results)
}()
for result := range results {
totalTracks += result
for range results {
totalTracks++
}
fmt.Println("Total tracks downloaded:", totalTracks)
return nil
return totalTracks, nil
}
@ -171,8 +178,7 @@ func getAudio(id, path, title, artist string) error {
return err
}
if songExists {
fmt.Println("Song exists: ", songKey)
return nil
return fmt.Errorf("song exists")
}
client := youtube.Client{}
@ -302,7 +308,7 @@ func correctFilename(title, artist string) (string, string) {
return title, artist
}
func processAndSaveSong(m4aFile, songName, songArtist string) error {
func processAndSaveSong(m4aFile, songName, songArtist, ytID string) error {
db, err := utils.NewDbClient()
if err != nil {
return fmt.Errorf("error connecting to DB: %d", err)
@ -313,16 +319,16 @@ func processAndSaveSong(m4aFile, songName, songArtist string) error {
songKey := fmt.Sprintf("%s - %s", songName, songArtist)
songExists, err := db.SongExists(songKey)
if err != nil {
return err
return fmt.Errorf("error checking if song exists: %v", err)
}
if songExists {
fmt.Println("Song exists: ", songKey)
return fmt.Errorf("error querying existing songs: %v", err)
return nil
}
// 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
// defer os.Remove(m4aFileMono)
audioBytes, err := ConvertM4aToMono(m4aFile, m4aFileMono)
if err != nil {
return fmt.Errorf("error converting M4A file to mono: %v", err)
@ -339,20 +345,17 @@ func processAndSaveSong(m4aFile, songName, songArtist string) error {
lines := strings.Split(string(output), "\n")
// bitDepth, _ := strconv.Atoi(strings.TrimSpace(lines[1]))
sampleRate, _ := strconv.Atoi(strings.TrimSpace(lines[0]))
fmt.Printf("SAMPLE RATE for %s: %v", songName, sampleRate)
audioInfo := shazam.AudioInfo{
SongName: songName,
SongArtist: songArtist,
BitDepth: 2,
Channels: 1,
SamplingRate: sampleRate,
chunkTag := shazam.ChunkTag{
SongName: songName,
SongArtist: songArtist,
YouTubeID: ytID,
}
fmt.Println("AUDIO INFO: ", audioInfo)
// Calculate fingerprints
chunks := shazam.Chunkify(audioBytes)
_, fingerprints := shazam.FingerprintChunks(chunks, &audioInfo)
_, fingerprints := shazam.FingerprintChunks(chunks, &chunkTag)
// Save fingerprints to MongoDB
for fgp, chunkData := range fingerprints {

View file

@ -20,6 +20,8 @@ type ResourceEndpoint struct {
type Track struct {
Title, Artist, Album string
Artists []string
Duration int
}
const (
@ -106,11 +108,11 @@ func TrackInfo(url string) (*Track, error) {
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(),
}
// 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
@ -128,6 +130,14 @@ func TrackInfo(url string) (*Track, error) {
}
}
track := &Track{
Title: gjson.Get(jsonResponse, "data.trackUnion.name").String(),
Artist: gjson.Get(jsonResponse, "data.trackUnion.firstArtist.items.0.profile.name").String(),
Artists: allArtists,
Duration: int(gjson.Get(jsonResponse, "data.trackUnion.duration.totalMilliseconds").Int()),
Album: gjson.Get(jsonResponse, "data.trackUnion.albumOfTrack.name").String(),
}
fmt.Println("ARTISTS: ", allArtists)
fmt.Println("TRACK: ", track)

View file

@ -1,24 +1,12 @@
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 {
@ -38,91 +26,6 @@ func ToLowerCase(s string) string {
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 {
@ -133,57 +36,6 @@ func GetFileSize(file string) (int64, error) {
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 {
@ -192,28 +44,6 @@ func DeleteFile(filePath string) {
}
}
/* 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)
@ -229,11 +59,20 @@ func ConvertM4aToMono(inputFile, outputFile string) ([]byte, error) {
channels := strings.TrimSpace(string(output))
if channels != "1" {
// Convert to mono
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)
}
// Resample to 8192 Hz
// resampledFile := strings.TrimSuffix(outputFile, filepath.Ext(outputFile)) + "_resampled.m4a"
// cmd = exec.Command("ffmpeg", "-i", outputFile, "-ar", "8192", resampledFile)
// output, err = cmd.CombinedOutput()
// if err := cmd.Run(); err != nil {
// return nil, fmt.Errorf("error resampling: %v, %v", err, string(output))
// }
audioBytes, err = ioutil.ReadFile(outputFile)
if err != nil {
return nil, fmt.Errorf("error reading input file: %v", err)

View file

@ -5,6 +5,15 @@ import (
"fmt"
"log"
"errors"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/buger/jsonparser"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
@ -37,3 +46,169 @@ func VideoID(spTrack Track) (string, error) {
// TODO: Handle when the query returns no songs (highly unlikely since the query is coming from spotify though)
return "", nil
}
var httpClient = &http.Client{}
var durationMatchThreshold = 5
type SearchResult struct {
Title, Uploader, URL, Duration, ID string
Live bool
SourceName string
Extra []string
}
func convertStringDurationToSeconds(durationStr string) int {
splitEntities := strings.Split(durationStr, ":")
if len(splitEntities) == 1 {
seconds, _ := strconv.Atoi(splitEntities[0])
return seconds
} else if len(splitEntities) == 2 {
seconds, _ := strconv.Atoi(splitEntities[1])
minutes, _ := strconv.Atoi(splitEntities[0])
return (minutes * 60) + seconds
} else if len(splitEntities) == 3 {
seconds, _ := strconv.Atoi(splitEntities[2])
minutes, _ := strconv.Atoi(splitEntities[1])
hours, _ := strconv.Atoi(splitEntities[0])
return ((hours * 60) * 60) + (minutes * 60) + seconds
} else {
return 0
}
}
// GetYoutubeId takes the query as string and returns the search results video ID's
func GetYoutubeId(spTrack Track) (string, error) {
artists := strings.Join(spTrack.Artists, ", ")
songDurationInSeconds := spTrack.Duration * 60
searchQuery := fmt.Sprintf("'%s' %s %s", spTrack.Title, artists, spTrack.Album)
searchResults, err := ytSearch(searchQuery, 10)
if err != nil {
return "", err
}
if len(searchResults) == 0 {
errorMessage := fmt.Sprintf("no songs found for %s", searchQuery)
return "", errors.New(errorMessage)
}
// Try for the closest match timestamp wise
for _, result := range searchResults {
allowedDurationRangeStart := songDurationInSeconds - durationMatchThreshold
allowedDurationRangeEnd := songDurationInSeconds + durationMatchThreshold
resultSongDuration := convertStringDurationToSeconds(result.Duration)
if resultSongDuration >= allowedDurationRangeStart && resultSongDuration <= allowedDurationRangeEnd {
return result.ID, nil
}
}
// Else return the first result if nothing is found
return searchResults[0].ID, nil
}
func getContent(data []byte, index int) []byte {
id := fmt.Sprintf("[%d]", index)
contents, _, _, _ := jsonparser.Get(data, "contents", "twoColumnSearchResultsRenderer", "primaryContents", "sectionListRenderer", "contents", id, "itemSectionRenderer", "contents")
return contents
}
func ytSearch(searchTerm string, limit int) (results []*SearchResult, err error) {
ytSearchUrl := fmt.Sprintf(
"https://www.youtube.com/results?search_query=%s", url.QueryEscape(searchTerm),
)
req, err := http.NewRequest("GET", ytSearchUrl, nil)
if err != nil {
return nil, errors.New("cannot get youtube page")
}
req.Header.Add("Accept-Language", "en")
res, err := httpClient.Do(req)
if err != nil {
return nil, errors.New("cannot get youtube page")
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(res.Body)
if res.StatusCode != 200 {
return nil, errors.New("failed to make a request to youtube")
}
buffer, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, errors.New("cannot read response from youtube")
}
body := string(buffer)
splitScript := strings.Split(body, `window["ytInitialData"] = `)
if len(splitScript) != 2 {
splitScript = strings.Split(body, `var ytInitialData = `)
}
if len(splitScript) != 2 {
return nil, errors.New("invalid response from youtube")
}
splitScript = strings.Split(splitScript[1], `window["ytInitialPlayerResponse"] = null;`)
jsonData := []byte(splitScript[0])
index := 0
var contents []byte
for {
contents = getContent(jsonData, index)
_, _, _, err = jsonparser.Get(contents, "[0]", "carouselAdRenderer")
if err == nil {
index++
} else {
break
}
}
_, err = jsonparser.ArrayEach(contents, func(value []byte, t jsonparser.ValueType, i int, err error) {
if err != nil {
return
}
if limit > 0 && len(results) >= limit {
return
}
id, err := jsonparser.GetString(value, "videoRenderer", "videoId")
if err != nil {
return
}
title, err := jsonparser.GetString(value, "videoRenderer", "title", "runs", "[0]", "text")
if err != nil {
return
}
uploader, err := jsonparser.GetString(value, "videoRenderer", "ownerText", "runs", "[0]", "text")
if err != nil {
return
}
live := false
duration, err := jsonparser.GetString(value, "videoRenderer", "lengthText", "simpleText")
if err != nil {
duration = ""
live = true
}
results = append(results, &SearchResult{
Title: title,
Uploader: uploader,
Duration: duration,
ID: id,
URL: fmt.Sprintf("https://youtube.com/watch?v=%s", id),
Live: live,
SourceName: "youtube",
})
})
if err != nil {
return results, err
}
return results, nil
}

View file

@ -68,7 +68,7 @@ func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error
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)
// fmt.Println("DUPLICATE FINGERPRINT: ", chunkfgp)
update := bson.M{"$push": bson.M{"chunkData": chunkData}}
_, err := chunksCollection.UpdateOne(context.Background(), filter, update)
if err != nil {
@ -88,28 +88,6 @@ func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error
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"`
@ -119,53 +97,6 @@ type chunkData struct {
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")