Downloading artist artwork en masse from Discogs using MusicBrainz

7 November 2025

I've started using Lightweight Music Server as my primary OpenSubsonic server for streaming my own music to my devices, and among other things one of its great features is that it allows you to download and link artist metadata using their MusicBrainz ID.

That said, LMS does not provide any mechanism to download this information automatically. And for many artists you might not want to download information automatically. But I want to, because I'm a weirdo with as of writing 14,882 files in the database and a couple hundred artists.

One of the cool things about running your own OpenSubsonic-compatible server is that you have access to a complete API! You can poll it yourself and have access to all sorts of great functions.

Because MusicBrainz is the main authority that I'm refernencing for all my music, I decided to see what the best options for downloading artist pictures was from there. MusicBrainz themselves does not host any images for artists, but does link to other services. They also have extensive compatibility with Flickr for identifying public photos. However, a cursory glance proved that my favorite band wasn't present in any public Flickr photos, so that would largely be useless to me.

Instead I poked around at the relationship attributes from their API pages, and quickly found it was very rudimentary to find Discogs pages for artists from their URL relationship. Discogs very straightforwardly lets you link to any artist's page with their unique Discogs ID, which you would also use to make API calls.

So this presented me a very rudimentary series of steps.

  1. Create a folder in my music folder for ArtistInfo
  2. Query LMS for the MusicBrainz IDs of all artists on the server
  3. Query MusicBrainz for any Discogs page for every artist who has an ID
  4. Query Discogs for any pictures for the artist
  5. Download those pictures and save them with their MusicBrainz ID.

The process for this turned out to be relatively straightforward, with a couple caveats:

  1. MusicBrainz and Discogs both require you to make at most 1 query every 1 second.
  2. You require a free account to download Discogs images

The first was easily managed by creating a wrapping function for all my calls that makes sure 1.1 seconds have elapsed (added a little bumper for safety). For the second: I just signed up. Painless.

So from there I sat down and spent an evening writing a Powershell script to do all this. And I present that script to you, to do with as you please! Please be nice.

Param(
    [switch]$Resume
);

$subsonicRoot = {{YOUR SUBSONIC SERVER}};
$subsonicKey = {{YOUR SUBSONIC API KEY}};
$subsonicVersion = {{YOUR SUBSONIC SERVER'S API VERSION}};

$discogsKey = {{YOUR DISCOGS KEY}};
$discogsSecret = {{YOUR DISCOGS SECRET}};

$userAgent = "ArtistInfoPowershellScript/0.0.1 ((YOUR CONTACT EMAIL))";

function RateLimit {
    Param([scriptblock]$Request)
    $startTime = (Get-Date);

    try { $res = $Request.Invoke() }
    catch { Write-Warning $_ }

    $elapsed = ((Get-Date) - $startTime).TotalSeconds;
    if ($elapsed -lt 1.1) { Start-Sleep -Seconds (1.1 - $elapsed); }
    if ($res) { return $res } else { return; };
}

If (!(Test-Path -PathType Container "ArtistInfo")) { New-Item -Type Directory "ArtistInfo" }

$mbToDiscogs = @{};

If ($Resume.IsPresent) {
    $mbToDiscogs = Get-Content -Path "mbToDiscogs.temp" -Raw | ConvertFrom-CliXml;
} else {
    $mbArtistIDs = ((Invoke-RestMethod -Uri "$subsonicRoot/getArtists?apiKey=$subsonicKey&c=$userAgent&v=$subsonicVersion" -Method "Get")."subsonic-response".artists.index.artist | Where-Object { $_.musicBrainzId }).musicBrainzId;

    For ($i = 0; $i -lt $mbArtistIDs.Count; $i++) {
        $mbID = $mbArtistIDs[$i];
        $pc = $i / $mbArtistIDs.Count * 100;
        
        if (!$mbToDiscogs[$mbId].IsPresent) {
            RateLimit({
                (Invoke-RestMethod -Uri "https://musicbrainz.org/ws/2/artist/$($mbID)?inc=url-rels&fmt=json" -UserAgent $userAgent -DisableKeepAlive -MaximumRetryCount 2).relations | Where-Object { $_.type -eq "Discogs" } | ForEach-Object {
                    $discogsID = ($_.url.resource -Split "/")[-1];
                    Write-Progress -Activity "Getting Artist ID" -Status "$mbID => $discogsID" -PercentComplete $pc;
                    try {
                        $mbToDiscogs.Add($mbID, $discogsID);
                    } catch [ArgumentException] {
                        # Nil.
                    }
                }
            })
        }
    }

    $mbToDiscogs | ConvertTo-CliXml | Set-Content -Path "mbToDiscogs.temp";
}

$mbToDiscogs.GetEnumerator() | ForEach-Object -Begin {
    $i = 0;
    $total = $mbToDiscogs.Keys.Count;
} -Process {
    $mbID = $_.Key;
    $discogsID = $_.Value;
    $pc = $i / $total * 100;

    Write-Progress -Activity "Getting artist $discogsID" -Status "Finding image for Artist" -PercentComplete $pc;
    $imageURI = RateLimit({
        $discogsArtist = (Invoke-RestMethod -Uri "https://api.discogs.com/artists/$discogsID" -Headers @{ 'Authorization' = "Discogs key=$discogsKey, secret=$discogsSecret" } -UserAgent $userAgent);
        try {
            return $discogsArtist.images[0].uri;
        } catch {
            Write-Information "No images available."
            return;
        }
    });

    if ($imageURI) {
        Write-Information "$mbID : $discogsID => $imageURI";

        Write-Progress -Activity "Getting artist $discogsID" -Status "Downloading image" -PercentComplete $pc;
        RateLimit({
            Invoke-RestMethod -Uri $imageURI -Headers @{ 'Authorization' = "Discogs key=$discogsKey, secret=$discogsSecret" } -UserAgent $userAgent -OutFile "./ArtistInfo/$mbID.jpeg"
        });
    }

    $i++;
}