Venture Write-up: Show Spotify lyrics on exterior show

Advertisements

[ad_1]

Hello guys! ?

It has been some time since I final posted something. I’ve been busy however I’m again with a enjoyable article. I got here throughout a job posting on Upwork the place somebody was in search of a software program that performs the lyrics of a Spotify track on twin screens. One display screen will show the English lyrics and the opposite one will show Hungarian lyrics. I used to be actually intrigued so I made a decision to provide it a go. On this article I’m going to speak about the entire thought course of I went by means of whereas making an attempt to determine an answer as I feel that half is usually lacking from programming tutorials.

Job posting

I discovered about a number of enjoyable issues whereas implementing this challenge. The primary one is MPRIS (Media Participant Distant Interfacing Specification). It’s a normal D-Bus interface that gives a typical programmatic API for controlling media gamers. Spotify helps MPRIS nonetheless, so far as I’m conscious, MPRIS is especially a Unix-specific expertise. It’s theoretically supported on Mac however I couldn’t discover a lot helpful details about it.

I didn’t find yourself utilizing MPRIS for this challenge however I wished to say it right here for my future self. And if you’re working with multimedia on a Unix-based system, you must undoubtedly test it out!

Sufficient with the prelude, let’s dive proper in.

Closing product

That is what I ended up making:

As you possibly can see, the lyrics are all synced up with the track. Solely the at the moment enjoying stanza is confirmed up on the display screen. The lyrics are loaded from native textual content information the place every track has an accompanying textual content file containing the lyrics.

Doing the analysis

As quickly as I learn the issue assertion, I made a decision to flex my Google-fu and run some searches. You may be stunned how usually there’s an open supply challenge doing precisely what you are attempting to do and you’ll repurpose it to your use case. Nonetheless, my searches didn’t lead to a lot success. I assume the primary motive for it was that that is such a distinct segment use case. Spotify already gives completely synced lyrics for songs and most of the people don’t want them translated. And even when they do, they will merely use Google translate.

Lyrics inside Spotify

I did come throughout two tasks on GitHub:

Nonetheless, neither of those tasks catered to my particular wants. The primary challenge was not cross-platform. It solely works on Linux and so I dominated it out straight away.

The second challenge was a bit extra helpful. It provides the identify of the at the moment enjoying track and the artist for that track. Most significantly, it was multi-platform. I figured that if I can get this to work on my machine, I can use the identify of the track and the artist to find an area textual content file containing the lyrics of the track and show them in a browser window utilizing Flask.

The one remaining concern was that the track and artist identify weren’t sufficient for me to show the lyrics. I wanted a approach to show solely the present stanza that was enjoying. SwSpotify didn’t have an API for me to get the present location of the playhead. I wanted that to determine which stanza to play. Fortunately, SwSpotify confirmed me a way that I may use to get this info. I noticed that it was utilizing Apple Script to question Spotify for the track info.

That is what the related code part appeared like:

apple_script_code = """
# Verify if Spotify is at the moment working
if software "Spotify" is working then
    # If this executes it means Spotify is at the moment working
    getCurrentlyPlayingTrack()
finish if
on getCurrentlyPlayingTrack()
    inform software "Spotify"
        set isPlaying to participant state as string
        set currentArtist to artist of present monitor as string
        set currentTrack to call of present monitor as string
        return {currentArtist, currentTrack, isPlaying}
    finish inform
finish getCurrentlyPlayingTrack
"""

Sadly I had no thought which queries have been supported by the Spotify software. I made a decision to run some Google searches once more and got here throughout this StackOverflow query. Somebody had helpfully talked about a file that contained all of the obtainable queries.

/Purposes/Spotify.app/Contents/Assets/Spotify.sdef

I shortly opened it up and noticed participant place. I examined it out within the Apple Script Editor and fortunately it labored straight away:

Apple Script Editor

That is the ultimate Apple Script that I ended up with:

if software "Spotify" is working then
    # If this executes it means Spotify is at the moment working
    getCurrentTrackPosition()
finish if
on getCurrentTrackPosition()
    inform software "Spotify"
        set trackPosition to participant place as string
        return trackPosition
    finish inform
finish getCurrentTrackPosition

After determining the Apple Script, I made a decision that I used to be going to arrange the lyrics information such that there was a timecode earlier than every stanza. I can then course of these lyrics information in Python and match the monitor place with the suitable stanza.

That is what the related part of one in all these information ended up wanting like:

[00:08.62]
First issues first
I'ma say all of the phrases inside my head
I am fired up and uninterested in the way in which that issues have been, oh ooh
The way in which that issues have been, oh ooh

[00:22.63]
Second factor second
Do not you inform me what you suppose that I may be
I am the one on the sail, I am the grasp of my sea, oh ooh
The grasp of my sea, oh ooh

At this level, the one lacking piece was to determine the right way to stream lyrics from Flask to the browser. I wished it to be so simple as doable so I didn’t need to use net sockets. Fortunately I had used streaming responses in Flask earlier than so I knew I may use them for this goal. I searched on-line for a ready-made instance and got here throughout this StackOverflow reply that contained some pattern code for me to make use of.

Closing code

I used the code from that reply and ended up with this remaining code:

from SwSpotify import spotify, SpotifyNotRunning, SpotifyPaused
from flask import Flask, render_template
import subprocess
import time
import re

app = Flask(__name__)


def get_current_time():
    apple_script_code = """
    # Verify if Spotify is at the moment working
    if software "Spotify" is working then
        # If this executes it means Spotify is at the moment working
        getCurrentTrackPosition()
    finish if
    on getCurrentTrackPosition()
        inform software "Spotify"
            set trackPosition to participant place as string
            return trackPosition
        finish inform
    finish getCurrentTrackPosition
    """

    outcome = subprocess.run(
        ["osascript", "-e", apple_script_code],
        capture_output=True,
        encoding="utf-8",
    )
    print(outcome.stdout)
    return outcome.stdout or ""


def get_sec(time_str):
    """Get seconds from time."""
    m, s = time_str.cut up(":")
    return int(m) * 60 + float(s)


def get_lyrics(song_name, language):
    attempt:
        with open(f"./{language}_lyrics/{song_name}.txt", "r") as f:
            lyrics = f.learn()
    besides FileNotFoundError:
        return
    sample = re.compile("[(.+)]((?:n.+)+)", re.MULTILINE)
    splitted = re.findall(sample, lyrics)
    time_stanza = {}
    for (time, stanza) in splitted:
        time_stanza[get_sec(time)] = stanza.strip()
    return time_stanza


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/stream")
def stream():
    def generate():
        whereas True:
            attempt:
                title, artist = spotify.present()
            besides (SpotifyNotRunning, SpotifyPaused) as e:
                print(e)
            else:
                print(f"{title} - {artist}")
                current_time = float(get_current_time())
                print(current_time)
                time_stanza = get_lyrics(title, "english")
                current_stanza = ""
                if time_stanza:
                    time_list = checklist(time_stanza.keys())
                    for index, stanza_start_time in enumerate(time_list):
                        if (
                            stanza_start_time < current_time
                            and time_list[index + 1] > current_time
                        ):
                            current_stanza = time_stanza[stanza_start_time]
                            break
                yield f"{title.title()} - {artist.title()} ##{current_stanza}n----"

            time.sleep(1)

    return app.response_class(generate(), mimetype="textual content/plain")


app.run()

That is the accompanying HTML template:

<!DOCTYPE html>
<html>

<head>

</head>

<physique>
    <div class="song-meta">
        <img src={{ url_for('static', filename='photographs/music.png' ) }} width="50" alt="track identify" />
        <span id="songName">Loading..</span>
    </div>
    <div class="lyrics">
        <pre id="stanza">
        </pre>
    </div>
    <script>

        var songNameSpan = doc.getElementById('songName');
        var stanzaSpan = doc.getElementById('stanza');
        var xhr = new XMLHttpRequest();
        xhr.open('GET', '{{ url_for('stream') }}');

        xhr.onreadystatechange = operate () {
            var all_lines = xhr.responseText.cut up('n----');
            last_line = all_lines.size - 2
            var songName_stanza = all_lines[last_line]
            if (songName_stanza){
                songName_stanza = songName_stanza.cut up('##')
                console.log(songName_stanza)
                songNameSpan.textContent = songName_stanza[0]
                stanzaSpan.textContent = songName_stanza[1]
            }

            if (xhr.readyState == XMLHttpRequest.DONE) {
                /*Typically it stops working when the stream is completed (track adjustments)
                so I simply refresh the web page. It virtually all the time solves the problem*/
                doc.location.reload()
                songNameSpan.textContent = "The Finish of Stream"
            }
        }

        xhr.ship();

    </script>

    <fashion>
        html,
        physique {
            top: 100%;
        }

        physique {
            margin: 0;
            background-color: #272727;
        }

        .song-meta {
            place: absolute;
            high: 40px;
            left: 40px;
            show: flex;
            align-items: middle;
        }

        #songName {
            font-size: 2rem;
            margin-left: 20px;
            background-color: white;
            padding: 10px 20px;
            border-radius: 20px;
        }

        .lyrics {
            top: 100%;
            padding: 0;
            margin: 0;
            show: flex;
            align-items: middle;
            justify-content: middle;
        }

        #stanza {
            width: auto;
            font-family: Helvetica, sans-serif;
            padding: 5px;
            margin: 10px;
            line-height: 1.5em;
            colour: white;
            font-size: 4rem;
            text-align: middle;
        }
    </fashion>
</physique>

</html>

My remaining listing construction appeared like this:

.
├── app.py
├── english_lyrics
│   └── Believer.txt
├── static
│   └── photographs
│       └── music.png
└── templates
    └── index.html

And that is what the ultimate software web site appeared like:

Final web app

Conclusion

There may be lots of stuff that isn’t optimized within the Python code. It is a fast and soiled answer to this drawback however it works completely advantageous. I didn’t work with the customer so I had no motive to enhance it. I simply wished to check out the thought as a result of the challenge appeared enjoyable ?

I had lots of enjoyable whereas engaged on this challenge. I hope you discovered about my thought course of on this article and noticed how one can go from level 0 to a totally working challenge and put totally different items collectively. Should you like studying about this sort of stuff please remark beneath. I like to listen to from you guys!

Until subsequent time! ?

[ad_2]