Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement audioscrobbler natively #4250

Merged
merged 11 commits into from
Dec 14, 2024
Merged

Reimplement audioscrobbler natively #4250

merged 11 commits into from
Dec 14, 2024

Conversation

toots
Copy link
Member

@toots toots commented Dec 10, 2024

This PR reimplements the audioscrobbler protocol natively.

The ocaml-lastfm package has been unmaintained for a long and uses the old now playing protocol so it was time we switched to the new protocol.

Also, since the last time we looked at it, the language is now mature enough to implement the support directly using liquidsoap script values!

The only drawback with the new protocol is that it needs both user credentials (username/password) and an API key. As much as we would love to provide a liquidsoap API key, last.fm terms of use for API keys seem to indicate that it is better to let users generate their own.

Changes

lastfm.* and librefm.* operators are removed.

New audioscrobbler operators are added, most importantly audioscrobbler.api.track.updateNowPlaying and audioscrobbler.api.track.scrobble

The operators are combined into audioscrobbler.submit which takes a source and applies updateNowPlaying when a track starts and scrobble when it ends.

Finally, global settings.audioscrobbler.api_key and settings.audioscrobbler.api_secret are added to make it possible to define API key and secret globally.

Usage example

settings.audioscrobbler.api_key := "..."
settings.audioscrobbler.api_secret := "..."

s = playlist("...")

s = audioscrobbler.submit(
  username="toots5446",
  password="hackme", 
 s
)

output.ao(fallible=true, s)

Fixes: #4230

@toots toots changed the base branch from main to xml-parse December 10, 2024 07:10
@toots toots force-pushed the lastfm-native branch 2 times, most recently from bdabbd1 to 11bcf39 Compare December 11, 2024 11:12
@toots toots marked this pull request as ready for review December 11, 2024 11:39
Base automatically changed from xml-parse to main December 12, 2024 11:15
@gAlleb
Copy link
Contributor

gAlleb commented Dec 12, 2024

Very cool, @toots, thank you!

It uses a source all the way now unlike (on_metadata) in the past. Is there a way to avoid scrobbling jingles? Like I did with previous version:

def lastfm.submit(~user,~password,~source="broadcast",~length=false) = 

fun (m) ->

if (m["jingle_mode"] != "true") then

  audioscrobbler.submit(user=user,password=password,
                source=source,length=length,
                host="post.audioscrobbler.com",port=80,
                m)

end

end

@toots
Copy link
Member Author

toots commented Dec 13, 2024

Very cool, @toots, thank you!

It uses a source all the way now unlike (on_metadata) in the past. Is there a way to avoid scrobbling jingles? Like I did with previous version:

def lastfm.submit(~user,~password,~source="broadcast",~length=false) = 

fun (m) ->

if (m["jingle_mode"] != "true") then

  audioscrobbler.submit(user=user,password=password,
                source=source,length=length,
                host="post.audioscrobbler.com",port=80,
                m)

end

end

Great idea!

I'm gonna add an optional metadata pre-processing callback. You should be able to bypass the process by returning an empty metadata list.

@gAlleb
Copy link
Contributor

gAlleb commented Dec 13, 2024

Cool! Thanks!

Could then show the usage of it when we have m["jingle_mode"] true? Also as I recall Azuracast annotates jingles this way too.

@toots toots enabled auto-merge December 14, 2024 12:53
@toots toots added this pull request to the merge queue Dec 14, 2024
Merged via the queue into main with commit e5d8a5f Dec 14, 2024
34 checks passed
@toots toots deleted the lastfm-native branch December 14, 2024 13:11
@savonet savonet deleted a comment from gAlleb Dec 14, 2024
@toots
Copy link
Member Author

toots commented Dec 14, 2024

Thanks for the feedback @gAlleb. I have removed the assert can you try now?

PS: You might want to disconnect your application here: https://www.last.fm/settings/applications and get a new session key.

@gAlleb
Copy link
Contributor

gAlleb commented Dec 14, 2024

Thanks, @toots. Works now and scrobbles!

In order to bypass scrobbling what shall I do?

  1. I can map matadata and remove artist and title in jingle-tracks before applying audioscrobbler. Is this a usecase?
    It works but what if I need this metadata further down the code chain. (For example some functions for sending statistics.)

  2. I can edit audioscrobbler.api.apply_meta function to accommodate check for m["jingle_mode'].
    This works but looks too hacky).

How can I disable scrobbling if m["jingle_mode"] metadata is found in a right way? (disregarding empty/not-empty artist/title)

@toots
Copy link
Member Author

toots commented Dec 14, 2024

I added a metadata_preprocessor to the audioscrobbler.submit function. If you return an empty list [] there it will disable the scrobbling process but won't affect your stream's metadata.

@toots
Copy link
Member Author

toots commented Dec 14, 2024

@gAlleb
Copy link
Contributor

gAlleb commented Dec 14, 2024

I got it.

def jingle_check(m) =
   jingle = m["jingle_mode"]
      if jingle == "true" then
         []
      else
        m
     end
end

radio = audioscrobbler.submit(
  username="",
  password="", 
  metadata_preprocessor=(fun(m) -> jingle_check(m)),
 radio
)

or simply metadata_preprocessor=(fun(m) -> if m["jingle_mode"] == "true" then [] else m end),

Something like this?


Also I've encountered unexpected behavior: When you scrobble tracks — all of them are scrobbled at the exact same time/time ago. It is the time when first track has been scrobbled since the start of LS.

Снимок экрана 2024-12-14 в 20 20 26

All of the above tracks have been played through from start to finish - so the time of scrobble should be different.

UPDATE: force=true doesn't seem to help with this.


Ok, I got it. Defining timestamp in function makes it freeze:

...
def audioscrobbler.api.track.scrobble(
  ~username,
  ~password,
  ~session_key=null(),
  ~api_key=null(),
  ~api_secret=null(),
  ~artist,
  ~track,

  ~timestamp=time(),

  ~album=null(),
  ~context=null(),
  ~streamId=null(),
  ~chosenByUser=true,
  ~trackNumber=null(),
  ~mbid=null(),
  ~albumArtist=null(),
  ~duration=null()
) =
  session_key =
...

We should remove it and use time() in params:

 params =
    [
      ("track", track),
      ("artist", artist),
      ("timestamp", string(time())),
...

Kinda works) Review is needed 😆

@toots
Copy link
Member Author

toots commented Dec 15, 2024

Great catch on the optional arg. This is a weird historical thing that we evaluate optional argument values at script eval not at function eval. Something we could change, I'll ask David why we did that.

Yes, returning empty metadata disables the process. It needs at least track title and artist to proceed. Think that this is user-friend enough?

@gAlleb
Copy link
Contributor

gAlleb commented Dec 15, 2024

Well, I got an idea right away but it took me some time to get the syntax right with metadata_preprocessor=

metadata_preprocessor=(fun(m) -> if m["jingle_mode"] == "true" then [] else m end),

It feels ok now :)

@toots
Copy link
Member Author

toots commented Dec 15, 2024

Ok cool. We could also allow the function to return null() which would be more explicit.

@gAlleb
Copy link
Contributor

gAlleb commented Dec 15, 2024

I think [] is fine too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Lastfm Submission failed: success!
2 participants