Scrobbling from Shazam on Android via Tasker (…and PHP)

TL; DR;

Schematically:

Shazam on Your Android in AutoShazam Mode -> Sends notifications to Android -> Tasker listens and grabs notifications -> Parses Notification with track metadata -> Sends track metadata to Last.FM trough PHP script

The long story

Usually I rely for scrobbling my musical listenings on my Android Phone that the Player Apps have its own scrobbling capabality built-in. Spotify has it, VLC has it also. But if the app doesn’t have it and it uses a fixed notification in your notification area with player controls on it, it’s just a matter of using the wonderful [Pano Scrobbler] application. This app detects automatically this kind of notification, grabs the track metadata and sends them to last.fm . This is fine if you are listening to the music in the very app which provides the notifications, but if you are listening from music from an external source not related to your phone like online radio, or youtube ? Yes, you can use Shazam or other similar like SoundHound, but only Shazam possess continuos recognizing, which is called AutoShazam Mode. Shazam sends the automatically recognitions to a notification, but Pano Scrobbler is not able to reasd them.
Enters Tasker, the one and most complete source for automating and programming something tailored to your needs and you can’t find an already made app for it: if you’re a programmer this is what you want ! Tasker can read all your notifications (if you give permission to do so, of course) and you can filter by the app that generated and them and their contents.

Tasker script

In the XML code below, replace yourserver.org with your domain.

<TaskerData sr="" dvi="1" tv="6.1.32">
    <dmetric>1080.0,2134.0</dmetric>
    <Profile sr="prof212" ve="2">
        <cdate>1691602106114</cdate>
        <clp>true</clp>
        <edate>1691601090420</edate>
        <flags>8</flags>
        <id>212</id>
        <mid0>211</mid0>
        <nme>Autoshazam Notifications</nme>
        <Event sr="con0" ve="2">
            <code>461</code>
            <pri>0</pri>
            <App sr="arg0">
                <appClass>com.shazam.android.activities.SplashActivity</appClass>
                <appPkg>com.shazam.android</appPkg>
                <label>Shazam</label>
            </App>
            <Str sr="arg1" ve="3"/>
            <Str sr="arg2" ve="3">A reproduzir*</Str>
            <Str sr="arg3" ve="3"/>
            <Str sr="arg4" ve="3"/>
            <Str sr="arg5" ve="3"/>
            <Str sr="arg6" ve="3"/>
            <Int sr="arg7" val="0"/>
        </Event>
    </Profile>
    <Project sr="proj0" ve="2">
        <cdate>1691601049652</cdate>
        <name>TaskerScrobblr</name>
        <pids>212</pids>
        <scenes>Popup</scenes>
        <tids>210,211</tids>
    </Project>
    <Scene sr="scenePopup">
        <cdate>1419546101360</cdate>
        <edate>1643196942800</edate>
        <gridSize>15</gridSize>
        <heightLand>-1</heightLand>
        <heightPort>370</heightPort>
        <nme>Popup</nme>
        <widthLand>-1</widthLand>
        <widthPort>444</widthPort>
        <TextElement sr="elements0" ve="3">
            <flags>4</flags>
            <geom>9,0,435,106,6,0,294,72</geom>
            <Str sr="arg0" ve="3">Title</Str>
            <Str sr="arg1" ve="3">Title</Str>
            <Int sr="arg2" val="12"/>
            <Int sr="arg3" val="100"/>
            <Str sr="arg4" ve="3">#FF0099CC</Str>
            <Str sr="arg5" ve="3"/>
            <Int sr="arg6" val="0"/>
            <Int sr="arg7" val="1"/>
            <Int sr="arg8" val="1"/>
        </TextElement>
        <RectElement sr="elements1">
            <flags>4</flags>
            <geom>0,106,444,4,0,106,444,4</geom>
            <Str sr="arg0" ve="3">Header</Str>
            <Int sr="arg1" val="0"/>
            <Str sr="arg2" ve="3">#77333333</Str>
            <Str sr="arg3" ve="3"/>
            <Int sr="arg4" val="0"/>
            <Str sr="arg5" ve="3">#FF000000</Str>
            <Int sr="arg6" val="0"/>
            <Int sr="arg7" val="0"/>
        </RectElement>
        <TextElement sr="elements2" ve="3">
            <clickTask>-936</clickTask>
            <flags>5</flags>
            <geom>0,148,444,197,10,116,423,216</geom>
            <Str sr="arg0" ve="3">Text1</Str>
            <Str sr="arg1" ve="3">Text</Str>
            <Int sr="arg2" val="10"/>
            <Int sr="arg3" val="100"/>
            <Str sr="arg4" ve="3">#FFFFFFFF</Str>
            <Str sr="arg5" ve="3"/>
            <Int sr="arg6" val="0"/>
            <Int sr="arg7" val="2"/>
            <Int sr="arg8" val="1"/>
            <RectElement sr="background">
                <flags>4</flags>
                <geom>-1,-1,-1,-1,-1,-1,-1,-1</geom>
                <Str sr="arg0" ve="3"/>
                <Int sr="arg1" val="0"/>
                <Str sr="arg2" ve="3"/>
                <Str sr="arg3" ve="3"/>
                <Int sr="arg4" val="0"/>
                <Str sr="arg5" ve="3">#FF000000</Str>
                <Int sr="arg6" val="0"/>
                <Int sr="arg7" val="0"/>
            </RectElement>
        </TextElement>
        <PropertiesElement sr="props">
            <Int sr="arg0" val="1"/>
            <Int sr="arg1" val="0"/>
            <Str sr="arg2" ve="3">#FF000000</Str>
            <Int sr="arg3" val="0"/>
            <Str sr="arg4" ve="3">Popup</Str>
            <Str sr="arg5" ve="3"/>
            <Img sr="arg6" ve="2"/>
            <Str sr="arg7" ve="3"/>
        </PropertiesElement>
    </Scene>
    <Task sr="task210">
        <cdate>1691528745485</cdate>
        <edate>1691589896085</edate>
        <id>210</id>
        <nme>Scrobble via your server</nme>
        <pri>100</pri>
        <Action sr="act0" ve="7">
            <code>37</code>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%artist</lhs>
                    <op>13</op>
                    <rhs></rhs>
                </Condition>
            </ConditionList>
        </Action>
        <Action sr="act1" ve="7">
            <code>360</code>
            <Bundle sr="arg0">
                <Vals sr="val">
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%input
Input
The text that was inserted in the dialog</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
                </Vals>
            </Bundle>
            <Str sr="arg1" ve="3">artist</Str>
            <Str sr="arg2" ve="3">Artist name? </Str>
            <Str sr="arg3" ve="3">%artist</Str>
            <Int sr="arg4" val="20"/>
            <Str sr="arg5" ve="3"/>
            <Int sr="arg6" val="0"/>
            <Int sr="arg7" val="0"/>
            <Str sr="arg8" ve="3"/>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%artist</lhs>
                    <op>13</op>
                    <rhs></rhs>
                </Condition>
            </ConditionList>
        </Action>
        <Action sr="act10" ve="7">
            <code>339</code>
            <se>false</se>
            <Bundle sr="arg0">
                <Vals sr="val">
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%http_cookies
Cookies
The cookies the server sent in the response in the Cookie:COOKIE_VALUE format. You can use this directly in the 'Headers' field of the HTTP Request action</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1>%http_data
Data
Data that the server responded from the HTTP request.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES1><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2>%http_file_output
File Output
Will always contain the file's full path even if you specified a directory as the File to save.</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES2><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3>%http_response_code
Response Code
The HTTP Code the server responded</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES3><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4>%http_headers()
Response Headers
The HTTP Headers the server sent in the response. Each header is in the 'key:value' format</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES4><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5>%http_response_length
Response Length
The size of the response in bytes</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES5></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
                </Vals>
            </Bundle>
            <Int sr="arg1" val="1"/>
            <Int sr="arg10" val="0"/>
            <Int sr="arg11" val="1"/>
            <Int sr="arg12" val="1"/>
            <Str sr="arg2" ve="3">http://yourserver.org/scrobble.php</Str>
            <Str sr="arg3" ve="3"/>
            <Str sr="arg4" ve="3"/>
            <Str sr="arg5" ve="3">artist=%artist&track=%track_name</Str>
            <Str sr="arg6" ve="3"/>
            <Str sr="arg7" ve="3"/>
            <Int sr="arg8" val="30"/>
            <Int sr="arg9" val="1"/>
        </Action>
        <Action sr="act11" ve="7">
            <code>548</code>
            <Str sr="arg0" ve="3">%http_data</Str>
            <Int sr="arg1" val="0"/>
            <Str sr="arg10" ve="3"/>
            <Int sr="arg11" val="1"/>
            <Int sr="arg12" val="0"/>
            <Str sr="arg13" ve="3"/>
            <Int sr="arg14" val="0"/>
            <Str sr="arg15" ve="3"/>
            <Int sr="arg2" val="0"/>
            <Str sr="arg3" ve="3"/>
            <Str sr="arg4" ve="3"/>
            <Str sr="arg5" ve="3"/>
            <Str sr="arg6" ve="3"/>
            <Str sr="arg7" ve="3"/>
            <Str sr="arg8" ve="3"/>
            <Int sr="arg9" val="1"/>
        </Action>
        <Action sr="act2" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%artist</Str>
            <Str sr="arg1" ve="3">%input</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act3" ve="7">
            <code>38</code>
        </Action>
        <Action sr="act4" ve="7">
            <code>37</code>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%track_name</lhs>
                    <op>13</op>
                    <rhs></rhs>
                </Condition>
            </ConditionList>
        </Action>
        <Action sr="act5" ve="7">
            <code>360</code>
            <Bundle sr="arg0">
                <Vals sr="val">
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES><StringArray sr=""><_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0>%input
Input
The text that was inserted in the dialog</_array_net.dinglisch.android.tasker.RELEVANT_VARIABLES0></StringArray></net.dinglisch.android.tasker.RELEVANT_VARIABLES>
                    <net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>[Ljava.lang.String;</net.dinglisch.android.tasker.RELEVANT_VARIABLES-type>
                </Vals>
            </Bundle>
            <Str sr="arg1" ve="3">track name? </Str>
            <Str sr="arg2" ve="3"/>
            <Str sr="arg3" ve="3">%track_name</Str>
            <Int sr="arg4" val="20"/>
            <Str sr="arg5" ve="3"/>
            <Int sr="arg6" val="0"/>
            <Int sr="arg7" val="0"/>
            <Str sr="arg8" ve="3"/>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%track_name</lhs>
                    <op>13</op>
                    <rhs></rhs>
                </Condition>
            </ConditionList>
        </Action>
        <Action sr="act6" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%track_name</Str>
            <Str sr="arg1" ve="3">%input</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act7" ve="7">
            <code>38</code>
        </Action>
        <Action sr="act8" ve="7">
            <code>550</code>
            <on>false</on>
            <Str sr="arg0" ve="3"/>
            <Str sr="arg1" ve="3">api_key=%lasfm_api_key&artist=%artist&method=track.scrobble&sk=%lasfm_sk&timestamp=%timestamp&track=%track_name&format=json&api_sig=%api_sig</Str>
            <Str sr="arg2" ve="3"/>
            <Str sr="arg3" ve="3">Popup</Str>
            <Int sr="arg4" val="16"/>
            <Int sr="arg5" val="1"/>
        </Action>
        <Action sr="act9" ve="7">
            <code>30</code>
            <on>false</on>
            <Int sr="arg0" val="0"/>
            <Int sr="arg1" val="10"/>
            <Int sr="arg2"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
        </Action>
    </Task>
    <Task sr="task211">
        <cdate>1691505070601</cdate>
        <edate>1691592228244</edate>
        <id>211</id>
        <nme>Scrobble Shazamed</nme>
        <pri>6</pri>
        <Action sr="act0" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%label</Str>
            <Str sr="arg1" ve="3">%evtprm3</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act1" ve="7">
            <code>598</code>
            <Str sr="arg0" ve="3">%label</Str>
            <Str sr="arg1" ve="3">A reproduzir</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Str sr="arg5" ve="3"/>
            <Int sr="arg6" val="1"/>
            <Str sr="arg7" ve="3"/>
        </Action>
        <Action sr="act10" ve="7">
            <code>130</code>
            <Str sr="arg0" ve="3">Scrobble via yourserver.org</Str>
            <Int sr="arg1">
                <var>%priority</var>
            </Int>
            <Int sr="arg10" val="1"/>
            <Str sr="arg2" ve="3">%artist</Str>
            <Str sr="arg3" ve="3">%title</Str>
            <Str sr="arg4" ve="3"/>
            <Int sr="arg5" val="0"/>
            <Int sr="arg6" val="1"/>
            <Str sr="arg7" ve="3"/>
            <Int sr="arg8" val="0"/>
            <Int sr="arg9" val="0"/>
        </Action>
        <Action sr="act11" ve="7">
            <code>192</code>
            <Int sr="arg0" val="1"/>
            <Str sr="arg1" ve="3">Merope</Str>
            <Int sr="arg2" val="5"/>
        </Action>
        <Action sr="act12" ve="7">
            <code>548</code>
            <Str sr="arg0" ve="3">Shazamed %artist - %track_name</Str>
            <Int sr="arg1" val="0"/>
            <Str sr="arg10" ve="3"/>
            <Int sr="arg11" val="1"/>
            <Int sr="arg12" val="0"/>
            <Str sr="arg13" ve="3"/>
            <Int sr="arg14" val="0"/>
            <Str sr="arg15" ve="3"/>
            <Int sr="arg2" val="0"/>
            <Str sr="arg3" ve="3"/>
            <Str sr="arg4" ve="3"/>
            <Str sr="arg5" ve="3"/>
            <Str sr="arg6" ve="3"/>
            <Str sr="arg7" ve="3"/>
            <Str sr="arg8" ve="3"/>
            <Int sr="arg9" val="1"/>
        </Action>
        <Action sr="act13" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%Last_track</Str>
            <Str sr="arg1" ve="3">%track_name</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act14" ve="7">
            <code>38</code>
        </Action>
        <Action sr="act2" ve="7">
            <code>590</code>
            <Str sr="arg0" ve="3">%label</Str>
            <Str sr="arg1" ve="3">de</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
        </Action>
        <Action sr="act3" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%track_name</Str>
            <Str sr="arg1" ve="3">%label1</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act4" ve="7">
            <code>547</code>
            <Str sr="arg0" ve="3">%artist</Str>
            <Str sr="arg1" ve="3">%label2</Str>
            <Int sr="arg2" val="0"/>
            <Int sr="arg3" val="0"/>
            <Int sr="arg4" val="0"/>
            <Int sr="arg5" val="3"/>
            <Int sr="arg6" val="1"/>
        </Action>
        <Action sr="act5" ve="7">
            <code>550</code>
            <on>false</on>
            <Str sr="arg0" ve="3"/>
            <Str sr="arg1" ve="3">%track_name = %Last_track?</Str>
            <Str sr="arg2" ve="3"/>
            <Str sr="arg3" ve="3">Popup</Str>
            <Int sr="arg4" val="5"/>
            <Int sr="arg5" val="1"/>
        </Action>
        <Action sr="act6" ve="7">
            <code>37</code>
            <on>false</on>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%track_name</lhs>
                    <op>3</op>
                    <rhs>%Last_track</rhs>
                </Condition>
            </ConditionList>
        </Action>
        <Action sr="act7" ve="7">
            <code>548</code>
            <on>false</on>
            <Str sr="arg0" ve="3">Track didn't change! </Str>
            <Int sr="arg1" val="0"/>
            <Str sr="arg10" ve="3"/>
            <Int sr="arg11" val="1"/>
            <Int sr="arg12" val="0"/>
            <Str sr="arg13" ve="3"/>
            <Int sr="arg14" val="0"/>
            <Str sr="arg15" ve="3"/>
            <Int sr="arg2" val="0"/>
            <Str sr="arg3" ve="3"/>
            <Str sr="arg4" ve="3"/>
            <Str sr="arg5" ve="3"/>
            <Str sr="arg6" ve="3"/>
            <Str sr="arg7" ve="3"/>
            <Str sr="arg8" ve="3"/>
            <Int sr="arg9" val="1"/>
        </Action>
        <Action sr="act8" ve="7">
            <code>38</code>
            <on>false</on>
        </Action>
        <Action sr="act9" ve="7">
            <code>37</code>
            <ConditionList sr="if">
                <Condition sr="c0" ve="3">
                    <lhs>%track_name</lhs>
                    <op>3</op>
                    <rhs>%Last_track</rhs>
                </Condition>
            </ConditionList>
        </Action>
    </Task>
</TaskerData>

Save the code as ScrobbleShazam.prj.xml. Copy to your Android Phone and store it in MARKDOWN_HASH72f2ff9cd151668e4455c7cb9fc4230fMARKDOWNHASH. If you tap on the lower bar it must appear an Import project option.
First, on this project I tried to send the scrobble requests directly from Tasker but every time I tried it, the server refused the request with a 403 error (badly signed request). According to the specs, you have to calculate a MD5 checksum using all the arguments of the method. Tasker doesn’t have this feature, you have to use external code to implement it, but i used an external javascript library, and calculated the checksum without errors last.fm API server refused it, even after having sucess after implementing it with Python code. You can also download the [script here](https://codehouse.digfish.org/wp-content/uploads/2023/08/ScrobleShazam.prj
.zip) .

The PHP script

The PHP script called by the Tasker project uses data from an application [created by you in Last.FM apps](Create API account | Last.fm). It will generate an API Key and a secret which you’ll use in the PHP script. Besides these two, you’all also need a session key (which is actually a access token). To get this, you can use the following python script:

import pylast

skg = pylast.SessionKeyGenerator(network)
url = skg.get_web_auth_url()

print(f"Please authorize this script to access your account: {url}\n")
import time
import webbrowser

webbrowser.open(url)
session_key = skg.get_web_auth_session_key(url)

print("Your session key is", session_key)

First install the pylast package with pip install pylast and you grab the session key.

The PHP Code:

<?php

define('LASTFM_API_KEY', '');
define('LASTFM_API_SECRET', '');
define('LASTFM_SK', '');

if (!empty($_POST)) {

    $postdata = array(
        'api_key' => LASTFM_API_KEY,
        'artist' => trim($_POST['artist']),
        'method' => 'track.scrobble',
        'sk' => LASTFM_SK,
        'timestamp' => time(),
        'track' => trim($_POST['track'])
    );
    $aux = '';
    foreach ($postdata as $key => $value) {
        $aux .= $key . $value;
    }

    $aux .= LASTFM_API_SECRET;
    $api_sig = md5($aux);

    $postdata['api_sig'] = $api_sig;
    $postdata['format'] = 'json';

    $ctx = stream_context_create(array(
        'http' => array(
            'method' => 'POST',
            'header' => 'Content-Type: application/x-www-form-urlencoded',
            'timeout' => 1,
            'content' => http_build_query($postdata)
        )
    ));

    $result = file_get_contents('http://ws.audioscrobbler.com/2.0/', false, $ctx);

    var_dump($result);
}
?>

<form action='<?php echo $_SERVER['PHP_SELF'] ?>' method='POST'>
    <label>
        artist
    </label>
    <input type='text' name='artist'>
    <label>
        track
    </label>
    <input type='text' name='track'>
    <button type='submit'>Scrobble</button>
</form>

The HTML Form below is not necessary but is only needed if you want first to test the script to see if everything is working fine without having to use the Tasker script. Replace the three constants at the start of the script and that’s all ! Happy shazaming radio songs!

PS: The Autoshazam feature normally can recognize the same song more than once in a consecutive way, but the tasker script automatically detects this and doesn’t scrobble the song if an initial scrobble was already sent.


Posted

in

by

Tags: