Genre tags

The issue

Funkwhale offers users a facility to assign genre tags to items such as tracks, albums, and artists. The tags_tag table is populated automatically when new tags are found in uploaded content, and users can also enter custom tags. By default, the table is empty. This means that a user on a new pod won’t see any results when attempting to tag items in the frontend.

The solution

To provide the best experience for new Funkwhale users, we should pre-populate this table with genre tags from Musicbrainz. Doing this enables users to easily search for and select the tags they want to assign to their content without needing to create custom tags or upload tagged content.

Having these tags easily available also facilitates better tagging within Funkwhale in future, reducing the reliance on external tools such as Picard.

Feature behavior

Backend behavior

The tags_tag table contains the following fields:

Field

Data type

Description

Relations

Constraints

id

Integer

The randomly generated table ID

tags_taggeditem.tag_id foreign key

None

musicbrainz_id

UUID

The Musicbrainz genre tag id. Used to identify the tag in Musicbrainz fetches

None

None

name

String

The name of the tag. Assigned by Funkwhale during creation for use in URLs. Uses Pascal casing for consistency

None

Must be unique

creation_date

Timestamp with time zone

The date on which the tag was created

None

None

Musicbrainz fetch task

To keep Funkwhale’s database up-to-date with Musicbrainz’s genre tags, we must fetch information from Musicbrainz periodically. We can use the following endpoint to fetch the information:

https://musicbrainz.org/ws/2/genre/all

This endpoint accepts the application/json header for a JSON response. See the Musicbrainz API documentation for more information. The pagination can be controlled by passing the following options:

  • limit: the number of results to return

  • offset: the starting point of the page

The fetch task should fetch all pages, using the response genre-count to determine how many offset positions to pass.

{
  "genre-count": 1913,
  "genre-offset": 24,
  "genres": [
    {
      "disambiguation": "",
      "id": "243975aa-1250-4429-8bd3-97080af44cf7",
      "name": "afro trap"
    },
    {
      "name": "afro-cuban jazz",
      "id": "cdb11433-1ff1-4c88-be16-717567e1342f",
      "disambiguation": ""
    },
    {
      "name": "afro-funk",
      "disambiguation": "",
      "id": "fc00175b-2be9-4d73-ba91-27b3ca827223"
    },
    {
      "name": "afro-jazz",
      "disambiguation": "",
      "id": "6f33d775-b4e2-473c-a7db-e34c525cc52d"
    },
    {
      "disambiguation": "",
      "id": "a7e0229c-6e53-45f1-a6f2-a791e78b159e",
      "name": "afro-zouk"
    },
    {
      "disambiguation": "funk/soul + West African sounds",
      "id": "fcc58a18-9326-4c92-8b29-c294d44379c3",
      "name": "afrobeat"
    },
    {
      "id": "b8793fdb-bbc8-4418-a6f8-05eafbbe07ef",
      "disambiguation": "West African urban/pop music",
      "name": "afrobeats"
    },
    {
      "name": "afropiano",
      "id": "d42b567f-0952-424b-959d-bee6e5961cc0",
      "disambiguation": ""
    },
    {
      "disambiguation": "",
      "id": "52349b68-9cad-496e-8785-00d53f410246",
      "name": "afroswing"
    },
    {
      "name": "agbadza",
      "disambiguation": "",
      "id": "c6d1e78b-ac82-4bb8-89d5-21e3226dc906"
    },
    {
      "disambiguation": "",
      "id": "b8ae0a3c-5826-4104-9663-fe8f828effa9",
      "name": "agbekor"
    },
    {
      "name": "aggrotech",
      "disambiguation": "",
      "id": "c844c144-90a8-4288-981e-e38275592688"
    },
    {
      "name": "ahwash",
      "id": "4802e6e4-f403-41d1-8e58-76e5cf4df81d",
      "disambiguation": ""
    },
    {
      "id": "50cc5641-b4f9-40b7-bf7a-6d903ac6c1c5",
      "disambiguation": "",
      "name": "aita"
    },
    {
      "id": "aebbce35-0e8b-40e9-b04c-bebbbda124d0",
      "disambiguation": "",
      "name": "akishibu-kei"
    },
    {
      "name": "al jeel",
      "disambiguation": "",
      "id": "0f8d3ff4-8cda-42c4-b462-10352cd01606"
    },
    {
      "name": "algerian chaabi",
      "id": "998efb76-2f98-41c8-8c5f-74c32e405e9f",
      "disambiguation": ""
    },
    {
      "name": "algorave",
      "id": "e0a9d0d1-b86f-4344-82a9-022a84627087",
      "disambiguation": ""
    },
    {
      "name": "alloukou",
      "disambiguation": "",
      "id": "e367c884-d94d-4fba-abc4-8ac51d167ccf"
    },
    {
      "disambiguation": "",
      "id": "ef1d11cc-e70f-4885-ad6c-103f060d33b2",
      "name": "alpenrock"
    },
    {
      "disambiguation": "",
      "id": "5f9cba3d-1a9f-46cd-8c49-7ed78d1f3354",
      "name": "alternative country"
    },
    {
      "name": "alternative dance",
      "id": "8301f73c-9166-4108-bfeb-4fd22dc19083",
      "disambiguation": ""
    },
    {
      "name": "alternative folk",
      "id": "0b48a36c-630f-4ee7-8cf3-480e3dd8be65",
      "disambiguation": ""
    },
    {
      "name": "alternative hip hop",
      "disambiguation": "",
      "id": "924943cd-73c8-45c0-96eb-74f2a15e5d6e"
    },
    {
      "disambiguation": "",
      "id": "7c4d0994-4c49-4c74-8763-df27fc0084cc",
      "name": "alté"
    }
  ]
}

The fetch task should run initially upon first startup and then monthly thereafter. The pod admin must be able to disable this job or run it manually at their discretion.

The task should use the following logic:

  1. Call the Musicbrainz API to fetch new data

  2. Verify the listed entries against the Funkwhale tag table. The id field in the response should be checked against the musicbrainz_id field

  3. Any entries that do not currently exist in Funkwhale should be added with the following mapping:

Musicbrainz response field

Tags table column

Notes

id

musicbrainz_id

name

name

Funkwhale should automatically generate a Pascal cased name based on this entry

  1. If the name of a tag exactly matches a name in the Musicbrainz response but the tag has no musicbrainz_id, the musicbrainz_id should be populated

Frontend behavior

Tagged uploads

When a user uploads new content with genre tags, the tagged item should be linked to any existing tags and new ones should be created if they’re not found.

In-app tagging

When a user uploads new content with no genre tags, they should be able to select tags from a dropdown menu. This menu is populated with the tags from the database with the name shown in the list. When a tag is selected, the item is linked to the associated tag.

If a user inserts a new tag, Funkwhale should:

  1. Store the entered string as the tag’s name

  2. Associate the targeted object with the new tag

Search results

Users should be able to search for tags using Funkwhale’s in-app search. In search autocomplete and search results page, the name should be used. The name of the tag should be used to populate the search URL.

Cards

The name of the tag should be shown in pills against cards.

Admin options

If the admin of a server wants to disable MusicBrainz tagging, they should be able to toggle this in their instance settings. If the setting is disabled:

  • The sync task should stop running

Availability

  • Admin panel

  • App frontend

  • CLI

Responsible parties

  • Backend group:

    • Update the tracks table to support the new information

    • Update the API to support the new information, or create a new v2 endpoint

    • Create the new fetch task

    • Add admin controls for the new task

  • Documentation group:

    • Document the new task and settings for admins