Lookup missing cover arts & artist info from lastFM

Hi all,

as not all music in my library has cover arts I decided to try to dynamically get the cover image and some artist info for the playing album:

As usual, not an absolute step-by-step tutorial, but an “inspiration” :slight_smile: … will be happy to add additional details if required.

Webview url="/static/Visualization/CoverArt.html" height=6
Utility .js to get data from OH via REST
// ***
// * Required definitions:
// *  - var baseURL = "../";
// *
// ***
// * Required API
// * - Google jQuery: https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// * - jQuery CSV:    https://github.com/evanplaice/jquery-csv
// * - date.js:       http://www.datejs.com/
// ***

function GetGroupItemNames(groupItem) {
    var groupUrl = baseURL.concat("rest/items/").concat(groupItem);
    var groupData = GetData(groupUrl);
    var items = [];

    groupData.members.forEach(function (item) {
        items.push(item.name);
    });

    return items;
}

function GetOpenHABHistory(item, days, serviceId) {
    var csv = GetOpenItemHABHistoryCSV(item, days, serviceId);
    var arrayData = $.csv.toArrays(csv, {
        onParseValue: $.csv.hooks.castToScalar
    });

    return arrayData;
}

function GetData(itemUrl) {
    var itemValue = null;
    // http://api.jquery.com/jquery.getjson/
    $.ajax({
        // contentType: 'text/plain',
        contentType : 'application/json; charset=utf-8',
        // dataType : 'json',
        url: itemUrl,
        data: {},
        async: false,
        success: function (data) {
            if (data != "NULL") {
                itemValue = data;
            }
        }
    });

    return itemValue;
}

// JSON to CSV Converter
function ConvertToCSV(objArray) {
    var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
    var str = '';

    for (var i = 0; i < array.data.length; i++) {
        var line = '';
        for (var index in array.data[i]) {
            if (line !== '') line += ',';

            line += array.data[i][index];
        }

        str += line + '\r\n';
    }

    return str;
}

function GetOpenHABItemState(item) {
    var itemUrl = baseURL.concat("rest/items/").concat(item).concat("/state/");
    return GetData(itemUrl);
}

function GetOpenHABItem(item) {
    var itemUrl = baseURL.concat("rest/items/").concat(item);
    return GetData(itemUrl);
}

function GetOpenHABItemIntValue(item) {
    return Math.round(GetOpenHabItemState(item));
}

function GetOpenHABItemHistoryJSON(item, days = 1, service = "rrd4j") {
    var jsonData = null;
    // var numberOfMilliseconds = 86400 * 1000 * days;

    var startTimeObject = (new Date()).addDays(days * -1);
    // var endTimeObject = new Date();
    var _month = startTimeObject.getMonth() + 1;
    var _starttime = startTimeObject.getFullYear() + "-" + _month + "-" + startTimeObject.getDate() + "T" + startTimeObject.getHours() + ":" + startTimeObject.getMinutes() + ":" + startTimeObject.getSeconds();
    // var _endtime = endTimeObject.getFullYear() + "-" + endTimeObject.getMonth() + "-" + endTimeObject.getDate() + "T" + endTimeObject.getHours() + ":" + endTimeObject.getMinutes() + ":" + endTimeObject.getSeconds();

    var itemUrl = baseURL.concat("rest/persistence/items/").concat(item);
    itemUrl = itemUrl.concat("?serviceId=" + service);

    if (days != 1) {
        itemUrl = itemUrl.concat("&starttime=" + _starttime);
    }
    // itemUrl = itemUrl.concat("&endtime="   + _endtime);

    jsonData = GetData(encodeURI(itemUrl));
    return jsonData;
}

function GetOpenItemHABHistoryCSV(item, days = 1, service = "rrd4j") {
    var jsonData = GetOpenHABItemHistoryJSON(item, days, service);
    return ConvertToCSV(jsonData);
}

function GetParameter(parameterName) {
    var result = null,
        tmp = [];
    location.search.substr(1).split("&")
        .forEach(function (item) {
            tmp = item.split("=");
            if (tmp[0] === parameterName) {
                result = decodeURIComponent(tmp[1]);
            }
        });
    return result;
}

function DOMSetElementHeight(elementId, height) {
    var pageElement = document.getElementById(elementId);
    pageElement.style.height = height + "px";
}

function DOMSetElementWidth(elementId, width) {
    var pageElement = document.getElementById(elementId);
    pageElement.style.width = width + "px";
}

var WINDOWS_1252 = '\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ';

function Windows1252ToUTF8(binaryString) {
    var text = '';

    for (var i = 0; i < binaryString.length; i++) {
        text += WINDOWS_1252.charAt(binaryString.charCodeAt(i));
    }

    return text;
}

The .html to display the image
<html>

<head>
    <title>Cover art</title>

    <link rel="stylesheet" type="text/css" href="../css/visualizationStyle.css">

    <script type="text/javascript">
        var baseURL = "../../../";
        var pageWidth = window.innerWidth;
        var pageHeight = window.innerHeight;

    </script>
    <script type="text/javascript" src="../javascript/openHAB.js"></script>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

    <script type="text/javascript">
        function update() {
            var url = GetOpenHABItemState("ItemCoverArtURL");
            var artistInformation = GetOpenHABItemState("ItemArtistInformation");

            var coverArtElement = document.getElementById("CoverArt");
            var artistInfoElement = document.getElementById("ArtistInformation")

            try {
                var coverURL = unescape(JSON.parse(url));
                if (coverURL == "") {
                    coverURL = "http://192.168.0.73:9000/music/current/cover.jpg";
                }
                coverArtElement.src = coverURL;
            } catch (err) {
                coverArtElement.src = unescape(url);
            }
            try {
                artistInformation = unescape(JSON.parse(artistInformation));
                artistInformation = artistInformation.replace(/(?:\r\n|\r|\n)/g, '<br />');
            } catch (err) {}

            // artistInformation = artistInformation.substr(0, artistInformation.indexOf("User-contributed text"));
            artistInfoElement.innerHTML = artistInformation;

            setTimeout(update, 10000);
        }

        $(document).ready(function() {
            update();
        });

    </script>

    <style>
        img#CoverArt {
            height: 190px;
            width: 190px;
            -moz-box-shadow: -10px 10px 40px -8px;
            -webkit-box-shadow: -10px 10px 40px -8px;
            box-shadow: -10px 10px 40px -8px;
            -moz-border-radius: 20px;
            -webkit-border-radius: 20px;
            -khtml-border-radius: 20px;
            border-radius: 20px;
            float: right;
            margin-right: 8px;
            border: 4px solid black;
            margin-left: 10px;
        }
        
        p#ArtistInformation {
            color: gray;
            text-indent: 38px;
            font-style: italic;
            margin-top: 10px;
        }

    </style>

</head>

<body>
    <div>
        <img id="CoverArt" />
    </div>
    <div>
        <p id="ArtistInformation"></p>
    </div>
</body>

</html>
The rules

Artist info

rule UpdateArtistSummary
  when
    Item ItemRadioAlbum  changed
 or Item ItemRadioArtist changed
 or Item ItemRadioTitle changed
 or Item ItemSqueezeSelectedRadioStation changed
  then
    val String _url = "http://ws.audioscrobbler.com/2.0/";
    val String _commandAlbum = "?method=artist.getinfo";
    val String _artist = ItemRadioArtist.state.toString
    
    if (_artist != null && !_artist.isEmpty()) {
      val String _artistEncoded = URLEncoder::encode(_artist, 'UTF-8')
      val String _request = _url + _commandAlbum 
                                 + "&api_key=" + lastFMAPIKey 
                                 + "&artist="  + _artistEncoded
                                 + "&format=json"
                                 
      val String _json = sendHttpGetRequest(_request);
      
      var String _artistInfo = transform("JSONPATH", "$..bio.summary", _json) 
                                                          //.content to get more details
       
      ItemArtistInformation.postUpdate(_artistInfo)                       
    } else {
      ItemArtistInformation.postUpdate("")
    }
    
  end

Cover


rule UpdateRadioCover
  when
    Item ItemRadioAlbum  changed
 or Item ItemRadioArtist changed
 or Item ItemRadioTitle changed
 or Item ItemSqueezeSelectedRadioStation changed
  then
    val String _url = "http://ws.audioscrobbler.com/2.0/";
    val String _commandAlbum = "?method=album.getinfo";
    val String _commandTitle = "?method=track.getInfo";
    val String _album = ItemRadioAlbum.state.toString
    val String _title = ItemRadioTitle.state.toString
    val String _artist = ItemRadioArtist.state.toString
    
    var String _albumURL = "http://192.168.0.73:9000/music/current/cover.jpg";
    if ((_artist != null && !_artist.isEmpty()) && (_title != null && !_title.isEmpty())) {
       val String _artistEncoded = URLEncoder::encode(_artist, 'UTF-8')
       val String _titleEncoded = URLEncoder::encode(_title, 'UTF-8')
      
       val String _request = _url + _commandTitle 
                                  + "&api_key=" + lastFMAPIKey 
                                  + "&artist="  + _artistEncoded
                                  + "&track="   + _titleEncoded
                                  + "&format=json"
       // println(_request)
       val String _json = sendHttpGetRequest(_request);
       var String _lastFMAlbumURL = transform("JSONPATH", "$..image[3].#text", _json)
       _lastFMAlbumURL = _lastFMAlbumURL.replace("[","")
       _lastFMAlbumURL = _lastFMAlbumURL.replace("]","")
       if (_lastFMAlbumURL != null && !_lastFMAlbumURL.isEmpty()) {
         println(_lastFMAlbumURL)
         _albumURL = _lastFMAlbumURL;
       }
    } else if ((_artist != null && !_artist.isEmpty()) 
     && (_album  != null && !_album.isEmpty())) {
       val String _artistEncoded = URLEncoder::encode(_artist, 'UTF-8')
       val String _albumEncoded = URLEncoder::encode(_album, 'UTF-8')
       
       val String _request = _url + _commandAlbum 
                                  + "&api_key=" + lastFMAPIKey 
                                  + "&artist="  + _artistEncoded
                                  + "&album="   + _albumEncoded
                                  + "&format=json"
       
       val String _json = sendHttpGetRequest(_request);
       var String _lastFMAlbumURL = transform("JSONPATH", "$..image[3].#text", _json)
       _lastFMAlbumURL = _lastFMAlbumURL.replace("[","")
       _lastFMAlbumURL = _lastFMAlbumURL.replace("]","")
       if (_lastFMAlbumURL != null && !_lastFMAlbumURL.isEmpty()) {
         _albumURL = _lastFMAlbumURL;
       }
    } else if (ItemSqueezeSelectedRadioStation.state >= 0) {
      _albumURL = "/static/img/radio/" +  ItemSqueezeSelectedRadioStation.state + ".png"
    } else if (ItemSqueezeSelectedPodcast.state >= 0) {
      _albumURL = "/static/img/podcast/" +  ItemSqueezeSelectedPodcast.state + ".png"
    }
    ItemCoverArtURL.postUpdate(_albumURL)
  end

Moderators: Please feel free to wikify this article.

with kind regards,
Patrik

4 Likes

Nice. I would expand this to actually add the cover to my library. :yum: Looks great! Thanks for sharing.