DEV Community

Cover image for Scrobbling from Shazam on Android via Tasker (...and PHP)
Samuel Viana
Samuel Viana

Posted on • Originally published at codehouse.digfish.org

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>


Enter fullscreen mode Exit fullscreen mode

Save the code as ScrobbleShazam.prj.xml. Copy to your Android Phone and store it in /sdcard/Tasker/projects. 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 .

The PHP script

The PHP script called by the Tasker project uses data from an application created by you in Last.FM apps. 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)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Dev Diairies image

User Feedback & The Pivot That Saved The Project ↪️

We’re following the journey of a dev team building on the Stellar Network as they go from hackathon idea to funded startup, testing their product in the real world and adapting as they go.

Watch full video 🎥

Top comments (0)

AWS Q Developer image

Build your favorite retro game with Amazon Q Developer CLI in the Challenge & win a T-shirt!

Feeling nostalgic? Build Games Challenge is your chance to recreate your favorite retro arcade style game using Amazon Q Developer’s agentic coding experience in the command line interface, Q Developer CLI.

Participate Now

👋 Kindness is contagious

Dive into this thoughtful piece, beloved in the supportive DEV Community. Coders of every background are invited to share and elevate our collective know-how.

A sincere "thank you" can brighten someone's day—leave your appreciation below!

On DEV, sharing knowledge smooths our journey and tightens our community bonds. Enjoyed this? A quick thank you to the author is hugely appreciated.

Okay