mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-18 19:19:28 +02:00
lastfm: listens export
This commit is contained in:
parent
267018901b
commit
f9d25e3b6f
2 changed files with 96 additions and 5 deletions
|
@ -11,6 +11,7 @@ Scotty transfers your listens/scrobbles and favorite tracks between various musi
|
||||||
- Transfer loved tracks from Funkwhale to ListenBrainz
|
- Transfer loved tracks from Funkwhale to ListenBrainz
|
||||||
- Submit listens stored in a Rockbox `.scrobbler.log` file to ListenBrainz, Last.fm or Maloja
|
- Submit listens stored in a Rockbox `.scrobbler.log` file to ListenBrainz, Last.fm or Maloja
|
||||||
- Store your favorite tracks from Deezer as a JSPF playlist
|
- Store your favorite tracks from Deezer as a JSPF playlist
|
||||||
|
- Backup your listening history from ListenBrainz or Last.fm
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -44,7 +45,7 @@ deezer | ✓ | ⨯ | ✓ | -
|
||||||
dump | ⨯ | ✓ | ⨯ | ✓
|
dump | ⨯ | ✓ | ⨯ | ✓
|
||||||
funkwhale | ✓ | ⨯ | ✓ | -
|
funkwhale | ✓ | ⨯ | ✓ | -
|
||||||
jspf | - | ✓ | - | ✓
|
jspf | - | ✓ | - | ✓
|
||||||
lastfm | - | - | ✓ | ✓
|
lastfm | ✓ | ✓ | ✓ | ✓
|
||||||
listenbrainz | ✓ | ✓ | ✓ | ✓
|
listenbrainz | ✓ | ✓ | ✓ | ✓
|
||||||
maloja | ✓ | ✓ | ⨯ | ⨯
|
maloja | ✓ | ✓ | ⨯ | ⨯
|
||||||
scrobbler-log | ✓ | ✓ | ⨯ | ⨯
|
scrobbler-log | ✓ | ✓ | ⨯ | ⨯
|
||||||
|
|
|
@ -31,8 +31,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MaxItemsPerGet = 1000
|
MaxItemsPerGet = 1000
|
||||||
MaxListensPerRequest = 50
|
MaxListensPerGet = 200
|
||||||
|
MaxListensPerSubmission = 50
|
||||||
|
MaxPage = 1000000
|
||||||
)
|
)
|
||||||
|
|
||||||
type LastfmApiBackend struct {
|
type LastfmApiBackend struct {
|
||||||
|
@ -69,10 +71,98 @@ func (b *LastfmApiBackend) OAuth2Setup(token oauth2.TokenSource) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *LastfmApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
|
||||||
|
page := MaxPage
|
||||||
|
minTime := oldestTimestamp
|
||||||
|
perPage := MaxItemsPerGet
|
||||||
|
|
||||||
|
defer close(results)
|
||||||
|
|
||||||
|
// We need to gather the full list of listens in order to sort them
|
||||||
|
p := models.Progress{Total: int64(page)}
|
||||||
|
|
||||||
|
out:
|
||||||
|
for page > 0 {
|
||||||
|
args := lastfm.P{
|
||||||
|
"user": b.username,
|
||||||
|
"limit": MaxListensPerGet,
|
||||||
|
// last.fm includes the listen with the exact timestamp in the result
|
||||||
|
"from": oldestTimestamp.Add(time.Second).Unix(),
|
||||||
|
"page": page,
|
||||||
|
}
|
||||||
|
result, err := b.client.User.GetRecentTracks(args)
|
||||||
|
if err != nil {
|
||||||
|
results <- models.ListensResult{Error: err}
|
||||||
|
progress <- p.Complete()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count := len(result.Tracks)
|
||||||
|
if count == 0 {
|
||||||
|
// The page was outside of the result range, adjust and request again
|
||||||
|
if page > result.TotalPages {
|
||||||
|
page = result.TotalPages
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
listens := make(models.ListensList, 0, 2*perPage)
|
||||||
|
for _, scrobble := range result.Tracks {
|
||||||
|
timestamp, err := strconv.ParseInt(scrobble.Date.Uts, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
results <- models.ListensResult{Error: err}
|
||||||
|
progress <- p.Complete()
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
if timestamp > oldestTimestamp.Unix() {
|
||||||
|
p.Elapsed += 1
|
||||||
|
listen := models.Listen{
|
||||||
|
ListenedAt: time.Unix(timestamp, 0),
|
||||||
|
UserName: b.username,
|
||||||
|
Track: models.Track{
|
||||||
|
TrackName: scrobble.Name,
|
||||||
|
ArtistNames: []string{},
|
||||||
|
ReleaseName: scrobble.Album.Name,
|
||||||
|
RecordingMbid: models.MBID(scrobble.Mbid),
|
||||||
|
ArtistMbids: []models.MBID{},
|
||||||
|
ReleaseMbid: models.MBID(scrobble.Album.Mbid),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if scrobble.Artist.Name != "" {
|
||||||
|
listen.Track.ArtistNames = []string{scrobble.Artist.Name}
|
||||||
|
}
|
||||||
|
if scrobble.Artist.Mbid != "" {
|
||||||
|
listen.Track.ArtistMbids = []models.MBID{models.MBID(scrobble.Artist.Mbid)}
|
||||||
|
}
|
||||||
|
listens = append(listens, listen)
|
||||||
|
} else {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(listens)
|
||||||
|
minTime = listens[len(listens)-1].ListenedAt
|
||||||
|
page -= 1
|
||||||
|
|
||||||
|
results <- models.ListensResult{
|
||||||
|
Listens: listens,
|
||||||
|
Total: result.Total,
|
||||||
|
OldestTimestamp: minTime,
|
||||||
|
}
|
||||||
|
p.Total = int64(result.TotalPages)
|
||||||
|
p.Elapsed = int64(result.TotalPages - page)
|
||||||
|
progress <- p
|
||||||
|
}
|
||||||
|
|
||||||
|
results <- models.ListensResult{OldestTimestamp: minTime}
|
||||||
|
progress <- p.Complete()
|
||||||
|
}
|
||||||
|
|
||||||
func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
func (b *LastfmApiBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
|
||||||
total := len(export.Listens)
|
total := len(export.Listens)
|
||||||
for i := 0; i < total; i += MaxListensPerRequest {
|
for i := 0; i < total; i += MaxListensPerSubmission {
|
||||||
listens := export.Listens[i:min(i+MaxListensPerRequest, total)]
|
listens := export.Listens[i:min(i+MaxListensPerSubmission, total)]
|
||||||
count := len(listens)
|
count := len(listens)
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
break
|
break
|
||||||
|
|
Loading…
Add table
Reference in a new issue