Skip to content

Commit

Permalink
add same song over and over scrobbling capability
Browse files Browse the repository at this point in the history
  • Loading branch information
kitesi committed Jan 19, 2024
1 parent 6994b44 commit 6ab3c9e
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 38 deletions.
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Installing music](#installing-music)
- [Auto Completion](#auto-completion)
- [Lastfm Scrobbling](#lastfm-scrobbling)
- [Lastfm Suggestions](#lastfm-suggestions)
- [Android](#android)
- [Configuration](#configuration)

Expand Down Expand Up @@ -151,13 +152,19 @@ like, which is why I use my own personal bash completion. It can be found in

### Lastfm Scrobbling

While VLC does have built in lastfm scrobbling, I could not get it to work.
You can run a watch server to watch for playing songs every x seconds (defaulted to 10).
It uses playerctl under the hood, so you will need to have that installed. It also only
While VLC does have built in lastfm scrobbling, I could not get it to work
(edit: I actually got it to work, but it doesn't scrobble certain tracks and I
kinda already built this so whatever). You can run a watch server to watch for
playing songs every x seconds (defaulted to 10). It uses playerctl under the
hood, so you will need to have that installed and be on linux (in the future I
could add vlc tcp support so that non-linux users could also use). It also only
checks for the VLC player so other media players or something like youtube will
not be logged.

Currently, the scrobble detection is kinda poor. It follows the approach of minimizing false positives, so if you skip/seek around, it likely won't scrobble. Also, if you play the same song over and over it won't scrobble more than once (although this will be fixed in the future).
Currently, the scrobble detection is kinda poor. It follows the approach of
minimizing false positives, so if you skip/seek around, it likely won't
scrobble. Also, if you play the same song over and over it won't scrobble more
than once (although this will be fixed in the future).

Lastly, it follows the [lastfm standards](https://www.last.fm/api/scrobbling):

Expand All @@ -172,6 +179,14 @@ music lastfm --interval 20 --debug

I personally have this command start on startup, and redirect the output to `/tmp/music-lastfm.log`.

### Lastfm Suggestions

You can also get lastfm suggestions (on any OS, without authentication) with the suggest command.

```
music lastfm suggest username --limit 20
```

### Android

If you would like to use the one of main functionalities of querying on android,
Expand Down
73 changes: 39 additions & 34 deletions commands/lastfm/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ func watchForTracks(credentials Credentials, delay int, stdOut *log.Logger, stdE
currentTrackLength := 0.0
currentTrackLastPosition := 0.0
waitTime := time.Duration(delay) * time.Second

const realTimeErrorMargin = 10.0
var currentTrackFirstTimestamp int64

for {
Expand Down Expand Up @@ -368,55 +370,54 @@ func watchForTracks(credentials Credentials, delay int, stdOut *log.Logger, stdE
artist := metadata["xesam:artist"]
track := metadata["xesam:title"]

if artist != currentArtist || track != currentTrack {
if currentTrack != "" && currentTrackLength != -1.0 {
paddedLastPosition := currentTrackLastPosition + DEFAULT_INTERVAL_SECONDS - position
timeConditionPassed := -1.0

if paddedLastPosition > currentTrackLength/2.0 {
timeConditionPassed = currentTrackLength / 2.0
} else if paddedLastPosition > MIN_LISTEN_TIME {
timeConditionPassed = MIN_LISTEN_TIME
}
if ((artist != currentArtist || track != currentTrack) || position < currentTrackLastPosition) && currentTrack != "" && currentTrackLength != -1.0 {
paddedLastPosition := currentTrackLastPosition + float64(delay) - position
timeConditionPassed := -1.0

realTimePassed := float64(time.Now().Unix()-currentTrackFirstTimestamp) / time.Second.Seconds()
listenStats := fmt.Sprintf("listened for %.2f, real: %.2f, half len: %.2f, min: %d", paddedLastPosition, realTimePassed, currentTrackLength/2.0, MIN_LISTEN_TIME)
realTimeErrorMargin := 10.0
if paddedLastPosition > currentTrackLength/2.0 {
timeConditionPassed = currentTrackLength / 2.0
} else if paddedLastPosition > MIN_LISTEN_TIME {
timeConditionPassed = MIN_LISTEN_TIME
}

if timeConditionPassed == -1.0 {
stdOut.Printf("└── not scrobbling because it did not pass either listen condition (%s)", listenStats)
} else if realTimePassed > timeConditionPassed-realTimeErrorMargin {
reason := ""
realTimePassed := float64(time.Now().Unix()-currentTrackFirstTimestamp) / time.Second.Seconds()
listenStats := fmt.Sprintf("listened for %.2f, real: %.2f, half len: %.2f, min: %d", paddedLastPosition, realTimePassed, currentTrackLength/2.0, MIN_LISTEN_TIME)

if paddedLastPosition > currentTrackLength/2.0 {
reason = "it is over half way through"
} else {
reason = "it has been listened to for over the minimum listen time"
}
if timeConditionPassed == -1.0 {
stdOut.Printf("└── not scrobbling because it did not pass either listen condition (%s)", listenStats)
} else if realTimePassed > timeConditionPassed-realTimeErrorMargin {
reason := ""

stdOut.Printf("└── scrobbling because %s (%s)", reason, listenStats)
if paddedLastPosition > currentTrackLength/2.0 {
reason = "it is over half way through"
} else {
reason = "it has been listened to for over the minimum listen time"
}

scrobbleResponse, err := scrobble(credentials, currentArtist, currentTrack, currentTrackFirstTimestamp)
stdOut.Printf("└── scrobbling because %s (%s)", reason, listenStats)

if err != nil {
stdErr.Printf("└── last.fm api error - %s", err.Error())
}
scrobbleResponse, err := scrobble(credentials, currentArtist, currentTrack, currentTrackFirstTimestamp)

if scrobbleResponse.Scrobbles.Attr.Ignored == 1 {
stdErr.Printf("└── last.fm ignored this scrobble - %s", scrobbleResponse.Scrobbles.Scrobble.IgnoredMessage.Text)
}
if err != nil {
stdErr.Printf("└── last.fm api error - %s", err.Error())
}

} else {
stdOut.Printf("└── not scrobbling because while it did pass the time condition, the real time did not pass (%s)", listenStats)
if scrobbleResponse.Scrobbles.Attr.Ignored == 1 {
stdErr.Printf("└── last.fm ignored this scrobble - %s", scrobbleResponse.Scrobbles.Scrobble.IgnoredMessage.Text)
}

} else {
stdOut.Printf("└── not scrobbling because while it did pass the time condition, the real time did not pass (%s)", listenStats)
}
}

if artist != currentArtist || track != currentTrack {
currentTrack = track
currentArtist = artist
currentTrackLastPosition = position
currentTrackFirstTimestamp = time.Now().Unix()

stdOut.Printf("new song detected - %s - %s\n", artist, track)
stdOut.Printf("new song detected - %s - %s", artist, track)
length, err := strconv.ParseFloat(metadata["vlc:time"], 64)

if err != nil {
Expand All @@ -429,6 +430,11 @@ func watchForTracks(credentials Credentials, delay int, stdOut *log.Logger, stdE
currentTrackLength = length
}
} else {
if position < currentTrackLastPosition {
stdErr.Printf("└── playerctl - position went backwards")
currentTrackFirstTimestamp = time.Now().Unix()
}

currentTrackLastPosition = position
}

Expand All @@ -447,7 +453,6 @@ func watchRunner(args *LastfmWatchArgs) error {
signal.Notify(gracefulExit, syscall.SIGINT, syscall.SIGTERM)

lockFileName := path.Join(os.TempDir(), "music-lastfm.lock")
fmt.Println(lockFileName)
_, err = os.Stat(lockFileName)

if err == nil {
Expand Down

0 comments on commit 6ab3c9e

Please sign in to comment.