GSoC 2020: Add 'love/hate a recording' and 'delete a listen' support to ListenBrainz

Tags: #<Tag:0x00007f75756ee3c0> #<Tag:0x00007f75756ee2f8>

This idea is a proposal for the project Add ‘love/hate a recording’ support to ListenBrainz listed here.

Personal Information

Overview

ListenBrainz currently only displays the list of listens imported into a user’s profile and the basic stats about the artists that a user has listened to. There isn’t much a user can do with the recordings/listens imported into their profile. Love, hate and delete are the basic features that can be provided to the users. Love/hate feature will help in perceiving the user’s taste in music and hence refine the recommendations that are generated for them. Keeping in mind the decision to migrate to Timescale DB and upon a discussion on IRC here it was decided to club the delete a listen feature into the love/hate a recording project.

My proposal for this project aims to

  • Add the ‘love/hate a recording’ feature
  • Add the ‘delete a listen’ feature
  • Adding the UI components for the above features and revamping the ‘My Listens’ page.

Project Description

The ‘love/hate a recording’ feature

The love/hate (aka thumbs up/thumbs down) feature deals with associating ratings with recordings for the users. The users will be able to mark the recordings which they loved/hated the most. They will also be able to retrieve all their loved/hated tracks. The user will be able to execute these actions via API calls or their ListenBrainz dashboard.

How will these ratings get stored?

The ratings will be stored in a normal Postgres table in Timescale DB. A separate schema of the form rating(user_name, recording_MSID, rating_score, created) will be implemented to store the ratings (discussed in detail later).

Flow of data

When a user rates (love/hate) a recording via an API call or their ListenBrainz dashboard, the flow of data will be something like this:

The users will also be able to retrieve all the liked/hated recordings in a similar way. The results can be filtered based on user_names and recording_MSIDs. The flow of data, in this case, will be:

The ‘delete a listen’ feature

The ‘delete all listens’ feature has been implemented (see PR #726) but is yet to be put to production. What still remains missing is the ‘delete a listen’ feature which deals with deleting a particular listen from the user’s listen history.

The second part of my proposal deals with adding The ‘delete a listen’ feature to ListenBrainz. The user will be able to delete the listen via an API call or their ListenBrainz dashboard.

Flow of data

When a user deletes a listen as loved/hated via an API call or their ListenBrainz dashboard, the flow of data will be something like this:

The new ListenBrainz dashboard

Along with adding the ‘love/hate/delete’ features I plan to redesign the ‘My Listens’ page into a new dashboard. It will include the following sections:

  • Listen History: The users listen history.
  • Playlists: The playlists and recommendations generated for the user.
  • Stats: The stats for the user from their listen history.

The UI components and views will be built using React.js and Typescript. The UI mockups have been shared in the implementation section.

Implementation

UI components and views

I plan to make the following changes to the ListenBrainz website which will be implemented in the listenbrainz.webserver.template module:

New ListenBrainz dashboard

In order to implement ‘like/hate/delete’ features, I will be redesigning the ListenBrainz user dashboard. The complete flow can be accessed via the Figma link attached.

  • Mockup link (Figma): here
  • Demo video (Youtube): here

The dashboard will look like this:

Playlist section

The playlists generated for the users will be integrated in the new ListenBrainz dashboard. The design will also be similar to the dashboard design (see below).

  • Mockup link (Figma): here
  • Demo video (Youtube): here

The Playlist section will look like this:

Love button( image )

The user can ‘love’ a recording by clicking on this button adjacent to the recording. The ‘love’ rating for a recording can be removed by clicking the same button again. The state of the button will change accordingly.

  • Demo video (Youtube): here

An AJAX POST request to /user/<user_name>/rate is triggered to store the rating in the database.

Hate button( )

The user can ‘hate’ a recording by clicking on this button adjacent to the recording. The ‘hate’ rating for a recording can be removed by clicking the same button again. The state of the button will change accordingly.

  • Demo video (Youtube): here

An AJAX POST request to /user/<user_name>/rate is triggered to store the rating in the database.

Delete button( image )

The user can ‘delete’ a listen from their listen history by clicking on this button. The corresponding listen entry will disappear from the dashboard.

  • Demo video (Youtube): here

An AJAX POST request to /user/<user_name>/delete-listen is triggered and the corresponding listen entry is deleted from the database.

API endpoints

The following new endpoints will be implemented:

listenbrainz.webserver.views.user.py

    @user_bp.route("/<user_name>/rate",methods=["POST"])
    def rate(user_name):
    """ 
    Request parameters:
        recording_ID: The MessyBrainz ID of the recording
        rating(-1/0/+1): The rating given by the user
    """
    # Execute query in Timescale DB and return the new rating

    @user_bp.route("/<user_name>/delete-listen")
    def delete-listen(user_name):
    """ 
    Request parameters:
        recording_ID: The MessyBrainz ID of the recording
        listened_at: The timestamp of the listen
         """
    # Execute query in Timescale DB and return response status


listenbrainz.webserver.views.index.py

    @index_bp.route("/ratings")
    def ratings():
    """ 
    Fetches ratings for given user or recording
    Request parameters:
        user_name: The MusicBrainz ID of the user
        recording_ID: The MessyBrainz ID of the recording
    """
    # Execute query in Timescale DB and return the results

The rating schema

The rating schema to be implemented in Timescale DB can be described as:

                          Table "timescale_lb.rating"
     Column     |           Type           | Collation | Nullable | Default 
----------------+--------------------------+-----------+----------+---------
 user_name      | text                     |           | not null | 
 recording_msid | uuid                     |           | not null | 
 score          | smallint                 |           | not null | 
 created        | timestamp with time zone |           | not null | 
Indexes:
    "rating_pkey" PRIMARY KEY, btree (user_name, recording_msid)

SQL queries

Add new rating

The query will be executed when the user marks a recording as ‘loved/hated’:

("""
    INSERT INTO "rating" (user_name, recording_msid, score, created)
           VALUES (:user_name, :rec_ms_id, :score, :created_ts)
"""), {
       "user_name": user_name,
       "rec_ms_id": recording_msid,
       "score": score,
	   "created_ts": created_ts
	  }

To prevent SQL injection attacks, direct queries won’t be used to insert data. Rather they will be processed using SQLAlchemy (ORM tool).

Delete a rating

The query will be executed when the user unmarks a recording as ‘loved/hated’:

("""
    DELETE FROM "rating"
           WHERE user_name = :user_name AND "rec_ms_id" = :recording_msid
"""), {
       "user_name": user_name,
       "rec_ms_id": recording_msid,
	  }

Fetch ratings

The query returns ratings for a given user or recording. It has 3 cases:

  • Fetch ratings for given user:
    ("""
    SELECT (user_name, recording_msid, score) FROM "rating"
           WHERE user_name = :user_name 
    """), {
           "user_name": user_name,
    	  }
  • Fetch ratings for given recording:
    ("""
    SELECT (user_name, recording_msid, score) FROM "rating"
           WHERE user_name = :user_name AND "rec_ms_id" = :recording_msid 
    """), {
           "rec_ms_id": recording_msid,
    	  }
  • Fetch ratings for given user and recording:
   ("""
    SELECT (user_name, recording_msid, score) FROM "rating"
           WHERE "rec_ms_id" = :recording_msid AND "rec_ms_id" = :recording_msid
    """), {
           "user_name": user_name,
           "rec_ms_id": recording_msid,
    	  }

Delete a listen

The query deletes a listen from the user’s listen history:

("""
    DELETE FROM "listen"
           WHERE user_name = :user_name AND "rec_ms_id" = :recording_msid AND "listened_at" = :listen_ts
"""), {
       "user_name": user_name,
       "rec_ms_id": recording_msid,
       "listen_ts": listened_at,
	  }

Stretch goals

Tuning the recommendation algorithm

The major modifications in the algorithm are to be made to the data preprocessing step. This mainly involves modifying the candidate_sets fed to the algorithm to take the user’s ratings (love/hate) along with the listen count into consideration while calculating the user’s preferences. The ratings will be used to segregate the recordings into two categories (user’s preferences):

  • What kind of recordings a user loves.
  • What kind of recordings a user hates.

The basis of similarity between users will be refined and can be explained as follows:

Optional Ideas

  • The ‘love/hate an artist’ feature: Just like recordings we can also implement ‘love/hate an artist’ feature. It will help get a better idea of users preferred/not-so-preferred artists.

  • The ‘most loved/hated recordings’ view: This view will render a list of the recordings which have the highest number of loves or hates associated with them.

Timeline

Community Bonding (May 4, 2020 - June 1, 2020):

Spend time contributing towards Timescale DB so that we are done setting it up before GSoC starts. Also work on deciding all that I need to code and discuss the exact tweaks that are to be made in the recommendation algorithm. The expected output is a clear in-depth workflow for the coming 13 weeks.

Phase 1 (June 1, 2020 - June 30, 2020):

During this phase I plan to work on all the underlying functionalities needed to revamp the ‘My Listens’ page. This will include writing AJAX scripts for the ‘love/hate/delete’ actions, implementing the new API calls, implementing the rating schema and all the related SQL queries, modifying the RabbitMQ pipeline to support ratings, in addition to listens, modifying ListenBrainz data dumps to dump the ratings in addition to listens, and writing all the corresponding tests for these changes. The server side basis for ‘like/hate/delete’ will be set up by the end of this phase.

Phase 2 (June 30, 2020 - July 27, 2020):

I will proceed with designing the new ‘My Listens’ section. This involves creating React components for the listens list, the listen count board, the user’s listening activity graph and the sidebar to manage and switch between different sections in the dashboard. The associated front end tests will also be written in this phase.

Phase 3 (July 28, 2020 - August 31, 2020):

This phase will be focused on writing the frontend tests and designing the ‘Playlists’ section in the new dashboard design. This won’t be too time consuming as it’s quite relative to the new ‘My Listens’ section. If time persists I will proceed with the stretch goals and optional ideas.

After Summer of Code:

Continue working with ListenBrainz and start working on the stretch goals. Also explore and work on what new features that can be added to ListenBrainz.

Here is week by week schedule of the GSoC period:

  • Week 1: Write the API endpoints and AJAX scripts associated with ‘like/hate/delete’ features.
  • Week 2: Implement the rating schema and write the SQL queries for the above mentioned features.
  • Week 3: Modify the RabbitMQ pipeline to support ratings along with listens.
  • Week 4: Modify the ListenBrainz dumps to form dumps for ratings along with listens. Work on the tests for changes implemented so far. (Phase 1 evaluations here).
  • Week 5: Start revamping the ‘My Listens’ section. Start with designing the new listens list.
  • Week 6: Code the React components for the listen count board and the user’s listening activity graph.
  • Week 7: Code the sidebar for managing dashboard.
  • Week 8: BUFFER PERIOD. Sync up if behind schedule else continue. (Phase 2 evaluations here).
  • Week 9: Writing frontend tests for changes implemented so far.
  • Week 10: Work on designing the ‘Playlists’ section, incorporating the new listens list and designing React components for other things in the listens section.
  • Week 11: Write frontend tests for the ‘Playlists’ section.
  • Week 12: Wrap up the new dashboard design.
  • Week 13: JUDGEMENT TIME. Wrap up all my GSoC work and compile final submission (Final evaluations here).

The required documentation will be added side by side.

Detailed information about myself

I am currently studying in my Junior year at National Institute of Technology, Hamirpur. I started contributing towards ListenBrainz in January. Here is a list of PRs and issues I have worked on so far.

Tell us about the computer(s) you have available for working on your SoC project!

I have a Lenovo laptop with Intel i5 processor and 8 GB RAM, running Ubuntu 19.10.

When did you first start programming?

I started programming in grade 5, mostly in JAVA as part of school curriculum. I started with Python and Javascript during my first year of college and eventually fell in love with them.

What type of music do you listen to?

I mostly listen to hip-hop and rock music and sometimes soft Indian melodies. Some favourite artists of mine are: Shawn Mendes, Coldplay, Marshmello and Ed Sheeran. My all time favourites are: Señorita, Hymn for the Weekend, Bilionera and FRIENDS. Here is my ListenBrainz profile.

What aspects of the project you’re applying for (e.g., MusicBrainz, AcousticBrainz, etc.) interest you the most?

My inclination towards MetaBrainz started during 2018 when some of our seniors who did GSoC in MeB that year showed us some glimpses of the annual MetaBrainz summit. After GSoC 2019 I started researching about the organization and found my interest in the ListenBrainz project. I love songs and I love fiddling with data. What’s better than a lot of meta-data to explore.

Have you ever used MusicBrainz to tag your files?

No, not yet.

Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?

ListenBrainz is my first major Open Source project I have contributed to so far. I have done many open source projects on my own though. You can refer to my GitHub profile.

What sorts of programming projects have you done on your own time?

I have worked on EQT (a stock market analyzer which suggests stocks according to user preferences and current market trends), MakeMyResume (a real time resume maker) and Hack3.0 (an online dashboard for hackathon management) and some more.

How much time do you have available, and how would you plan to use it?

I can easily devote around 50 hours a week and sometimes more as it will be my summer vacations during most of the GSoC timeline.

Do you plan to have a job or study during the summer in conjunction with Summer of Code?

No.

1 Like

Hi everyone.
This is the initial draft of my proposal. Please give your valuable reviews on it. Thanks :slight_smile: