A couple more changes

This commit is contained in:
Chigozirim Igweamaka 2024-03-27 14:10:34 +01:00
parent a1ba649480
commit 439b5442f5
7 changed files with 214 additions and 108 deletions

View file

@ -13,6 +13,16 @@ function App() {
const [serverEngaged, setServerEngaged] = useState(false);
const [peerConnection, setPeerConnection] = useState();
function cleanUp() {
if (stream != null) {
stream.getTracks().forEach((track) => track.stop());
}
setOffer(null);
setStream(null);
setPeerConnection(null);
setServerEngaged(false);
}
// Function to initiate the client peer
function initiateClientPeer(stream = null) {
const peer = new Peer({
@ -71,8 +81,13 @@ function App() {
socket.on("matches", (matches) => {
matches = JSON.parse(matches);
if (matches) {
setMatches(matches);
console.log("Matches: ", matches);
} else {
console.log("No Matches");
}
cleanUp();
});
socket.on("downloadStatus", (msg) => {
@ -87,6 +102,20 @@ function App() {
console.log("Playlist stat: ", msg);
});
useEffect(() => {
const emitTotalSongs = () => {
socket.emit("totalSongs", "");
};
const intervalId = setInterval(emitTotalSongs, 8000);
return () => clearInterval(intervalId);
}, []);
socket.on("totalSongs", (totalSongs) => {
console.log("Total songs in DB: ", totalSongs);
});
const streamAudio = () => {
navigator.mediaDevices
.getDisplayMedia({ audio: true })
@ -109,7 +138,7 @@ function App() {
setOffer(JSON.stringify(data));
console.log("Offer should be reset");
});
setStream(stream); // Set the audio stream to state
setStream(stream);
})
.catch((error) => {
console.error("Error accessing user media:", error);

168
server.go
View file

@ -7,6 +7,7 @@ import (
"net/http"
"song-recognition/signal"
"song-recognition/spotify"
"song-recognition/utils"
"strings"
"github.com/gin-gonic/gin"
@ -43,9 +44,10 @@ func main() {
server := socketio.NewServer(nil)
server.OnConnect("/", func(s socketio.Conn) error {
s.SetContext("")
log.Println("CONNECTED: ", s.ID())
server.OnConnect("/", func(socket socketio.Conn) error {
socket.SetContext("")
log.Println("CONNECTED: ", socket.ID())
return nil
})
@ -56,72 +58,21 @@ func main() {
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)
server.OnEvent("/", "totalSongs", func(socket socketio.Conn) {
db, err := utils.NewDbClient()
if err != nil {
panic(err)
log.Printf("Error connecting to DB: %v", err)
return
}
defer db.Close()
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)
totalSongs, err := db.TotalSongs()
if err != nil {
panic(err)
}
jsonData, err := json.Marshal(matches[:5])
if err != nil {
fmt.Println("Log error: ", err)
log.Println("Log error getting total songs count:", 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()))
socket.Emit("totalSongs", totalSongs)
})
server.OnEvent("/", "newDownload", func(socket socketio.Conn, spotifyURL string) {
@ -178,13 +129,31 @@ func main() {
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
}
// check if track already exist
db, err := utils.NewDbClient()
if err != nil {
fmt.Errorf("Log - error connecting to DB: %d", err)
}
defer db.Close()
chunkTag, err := db.GetChunkTagForSong(trackInfo.Title, trackInfo.Artist)
if err != nil {
fmt.Println("chunkTag error: ", err)
}
if chunkTag != nil {
socket.Emit("downloadStatus", fmt.Sprintf(
"'%s' by '%s' already exists in the database (https://www.youtube.com/watch?v=%s)",
trackInfo.Title, trackInfo.Artist, chunkTag["youtubeid"]))
return
}
err = spotify.DlSingleTrack(spotifyURL, tmpSongDir)
if err != nil {
socket.Emit("downloadStatus", fmt.Sprintf("Failed to download '%s' by '%s'", trackInfo.Title, trackInfo.Artist))
@ -199,6 +168,79 @@ func main() {
}
})
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)
if len(matches) > 5 {
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.OnError("/", func(s socketio.Conn, e error) {
log.Println("meet error:", e)
})

View file

@ -26,7 +26,7 @@ const (
)
type ChunkTag struct {
SongName string
SongTitle string
SongArtist string
YouTubeID string
TimeStamp string
@ -45,14 +45,14 @@ func Match(sampleAudio []byte) ([]primitive.M, error) {
var chunkTags = make(map[string]primitive.M)
var songsTimestamps = make(map[string][]string)
for _, chunkfgp := range chunkFingerprints {
listOfChunkTags, err := db.GetChunkData(chunkfgp)
listOfChunkTags, err := db.GetChunkTags(chunkfgp)
if err != nil {
return nil, fmt.Errorf("error getting chunk data with fingerprint %d: %v", chunkfgp, err)
}
for _, chunkTag := range listOfChunkTags {
timeStamp := fmt.Sprint(chunkTag["timestamp"])
songKey := fmt.Sprintf("%s by %s", chunkTag["songname"], chunkTag["songartist"])
songKey := fmt.Sprintf("%s by %s", chunkTag["songtitle"], chunkTag["songartist"])
if songsTimestamps[songKey] == nil {
songsTimestamps[songKey] = []string{timeStamp}

View file

@ -308,7 +308,7 @@ func correctFilename(title, artist string) (string, string) {
return title, artist
}
func processAndSaveSong(m4aFile, songName, songArtist, ytID string) error {
func processAndSaveSong(m4aFile, songTitle, songArtist, ytID string) error {
db, err := utils.NewDbClient()
if err != nil {
return fmt.Errorf("error connecting to DB: %d", err)
@ -316,7 +316,7 @@ func processAndSaveSong(m4aFile, songName, songArtist, ytID string) error {
defer db.Close()
// Check if the song has been processed and saved before
songKey := fmt.Sprintf("%s - %s", songName, songArtist)
songKey := fmt.Sprintf("%s - %s", songTitle, songArtist)
songExists, err := db.SongExists(songKey)
if err != nil {
return fmt.Errorf("error checking if song exists: %v", err)
@ -345,10 +345,10 @@ func processAndSaveSong(m4aFile, songName, songArtist, ytID 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)
fmt.Printf("SAMPLE RATE for %s: %v", songTitle, sampleRate)
chunkTag := shazam.ChunkTag{
SongName: songName,
SongTitle: songTitle,
SongArtist: songArtist,
YouTubeID: ytID,
}
@ -358,8 +358,8 @@ func processAndSaveSong(m4aFile, songName, songArtist, ytID string) error {
_, fingerprints := shazam.FingerprintChunks(chunks, &chunkTag)
// Save fingerprints to MongoDB
for fgp, chunkData := range fingerprints {
err := db.InsertChunkData(fgp, chunkData)
for fgp, ctag := range fingerprints {
err := db.InsertChunkTag(fgp, ctag)
if err != nil {
return fmt.Errorf("error inserting document: %v", err)
}

View file

@ -138,9 +138,6 @@ func TrackInfo(url string) (*Track, error) {
Album: gjson.Get(jsonResponse, "data.trackUnion.albumOfTrack.name").String(),
}
fmt.Println("ARTISTS: ", allArtists)
fmt.Println("TRACK: ", track)
return track.buildTrack(), nil
}
@ -241,6 +238,8 @@ func (t *Track) buildTrack() *Track {
track := &Track{
Title: t.Title,
Artist: t.Artist,
Artists: t.Artists,
Duration: t.Duration,
Album: t.Album,
}

View file

@ -21,7 +21,7 @@ import (
const developerKey = "AIzaSyC3nBFKqudeMItXnYKEeOUryLKhXnqBL7M"
// https://github.com/BharatKalluri/spotifydl/blob/v0.1.0/src/youtube.go
func VideoID(spTrack Track) (string, error) {
func getYoutubeIdWithAPI(spTrack Track) (string, error) {
service, err := youtube.NewService(context.TODO(), option.WithAPIKey(developerKey))
if err != nil {
log.Fatalf("Error creating new YouTube client: %v", err)
@ -77,10 +77,9 @@ func convertStringDurationToSeconds(durationStr string) int {
}
// 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)
func GetYoutubeId(track Track) (string, error) {
songDurationInSeconds := track.Duration * 60
searchQuery := fmt.Sprintf("'%s' %s %s", track.Title, track.Artist, track.Album)
searchResults, err := ytSearch(searchQuery, 10)
if err != nil {
@ -99,6 +98,7 @@ func GetYoutubeId(spTrack Track) (string, error) {
return result.ID, nil
}
}
// Else return the first result if nothing is found
return searchResults[0].ID, nil
}

View file

@ -35,6 +35,16 @@ func (db *DbClient) Close() error {
return nil
}
func (db *DbClient) TotalSongs() (int, error) {
existingSongsCollection := db.client.Database("song-recognition").Collection("existing-songs")
total, err := existingSongsCollection.CountDocuments(context.Background(), bson.D{})
if err != nil {
return 0, err
}
return int(total), nil
}
func (db *DbClient) SongExists(key string) (bool, error) {
existingSongsCollection := db.client.Database("song-recognition").Collection("existing-songs")
filter := bson.M{"_id": key}
@ -59,7 +69,7 @@ func (db *DbClient) RegisterSong(key string) error {
return nil
}
func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error {
func (db *DbClient) InsertChunkTag(chunkfgp int64, chunkTag interface{}) error {
chunksCollection := db.client.Database("song-recognition").Collection("chunks")
filter := bson.M{"fingerprint": chunkfgp}
@ -67,9 +77,9 @@ func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error
var result bson.M
err := chunksCollection.FindOne(context.Background(), filter).Decode(&result)
if err == nil {
// If the fingerprint already exists, append the chunkData to the existing list
// If the fingerprint already exists, append the chunkTag to the existing list
// fmt.Println("DUPLICATE FINGERPRINT: ", chunkfgp)
update := bson.M{"$push": bson.M{"chunkData": chunkData}}
update := bson.M{"$push": bson.M{"chunkTags": chunkTag}}
_, err := chunksCollection.UpdateOne(context.Background(), filter, update)
if err != nil {
return fmt.Errorf("error updating chunk data: %v", err)
@ -80,7 +90,7 @@ func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error
}
// If the document doesn't exist, insert a new document
_, err = chunksCollection.InsertOne(context.Background(), bson.M{"fingerprint": chunkfgp, "chunkData": []interface{}{chunkData}})
_, err = chunksCollection.InsertOne(context.Background(), bson.M{"fingerprint": chunkfgp, "chunkTags": []interface{}{chunkTag}})
if err != nil {
return fmt.Errorf("error inserting chunk data: %v", err)
}
@ -88,16 +98,7 @@ func (db *DbClient) InsertChunkData(chunkfgp int64, chunkData interface{}) error
return nil
}
type chunkData struct {
SongName string `bson:"songName"`
SongArtist string `bson:"songArtist"`
BitDepth int `bson:"bitDepth"`
Channels int `bson:"channels"`
SamplingRate int `bson:"samplingRate"`
TimeStamp string `bson:"timeStamp"`
}
func (db *DbClient) GetChunkData(chunkfgp int64) ([]primitive.M, error) {
func (db *DbClient) GetChunkTags(chunkfgp int64) ([]primitive.M, error) {
chunksCollection := db.client.Database("song-recognition").Collection("chunks")
filter := bson.M{"fingerprint": chunkfgp}
@ -111,10 +112,45 @@ func (db *DbClient) GetChunkData(chunkfgp int64) ([]primitive.M, error) {
return nil, fmt.Errorf("error retrieving chunk data: %w", err)
}
var listOfChunkData []primitive.M
for _, data := range result["chunkData"].(primitive.A) {
listOfChunkData = append(listOfChunkData, data.(primitive.M))
var listOfChunkTags []primitive.M
for _, data := range result["chunkTags"].(primitive.A) {
listOfChunkTags = append(listOfChunkTags, data.(primitive.M))
}
return listOfChunkData, nil
return listOfChunkTags, nil
}
func (db *DbClient) GetChunkTagForSong(songTitle, songArtist string) (bson.M, error) {
chunksCollection := db.client.Database("song-recognition").Collection("chunks")
filter := bson.M{
"chunkTags": bson.M{
"$elemMatch": bson.M{
"songtitle": songTitle,
"songartist": songArtist,
},
},
}
var result bson.M
if err := chunksCollection.FindOne(context.Background(), filter).Decode(&result); err != nil {
if err == mongo.ErrNoDocuments {
return nil, nil
}
return nil, fmt.Errorf("error finding chunk: %v", err)
}
var chunkTag map[string]interface{}
for _, chunk := range result["chunkTags"].(primitive.A) {
chunkMap, ok := chunk.(primitive.M)
if !ok {
continue
}
if chunkMap["songtitle"] == songTitle && chunkMap["songartist"] == songArtist {
chunkTag = chunkMap
break
}
}
return chunkTag, nil
}