Building a Music Controlling Python Start Page for Linux

Robert Washbourne - 3 years ago - programming, themes, python

By using the Gnome GLib apis, we can use the Playerctl Linux package with Python. Bundling this with bottle, a simple Python server, and websockets to listen for updates, we have a music controlling homepage.

Dependencies

You will of course need Python. Here, I use python2 because gevent (the websocket) does not support Python3.

Linux packages you need:

Python dependencies (install with pip):

bottle

bottle-websocket

Github

You can download all the code for this project on DevPy's github here.

You can clone this to your computer with

git clone https://github.com/devpytech/musicpage.git

Adding a websocket

First we need to import the (extensive) list of packages.

#!/usr/bin/env python2 from bottle.ext.websocket import GeventWebSocketServer import gi gi.require_version('Playerctl', '1.0') from gi.repository import GLib, GObject, Playerctl from bottle.ext.websocket import websocket from geventwebsocket.exceptions import WebSocketError from bottle import run, get import threading import os from gevent import monkey monkey.patch_all() print("starting websocket")

Adding a route

When the main page connects to the websocket, we send the attributes so we can update the display.

@get('/websocket', apply=[websocket]) #provide a websocket connection def echo(ws): print("connected") # send the music status ws.send('%s,%s,%s,%s' % (os.popen("playerctl metadata mpris:artUrl").read(), Playerctl.Player().get_title(), Playerctl.Player().get_artist(), Playerctl.Player().get_property("status")))

Playerctl process

We can make a new Playerctl player with player = Playerctl.Player() and run a loop to check for updates with player.on('metadata', on_track_change) and GLib.MainLoop().run() . When the track is updated, we send a message to the webpage.

@get('/websocket', apply=[websocket]) def echo(ws): print("connected") # send the music status ws.send('%s,%s,%s,%s' % (os.popen("playerctl metadata mpris:artUrl").read(), Playerctl.Player().get_title(), Playerctl.Player().get_artist(), Playerctl.Player().get_property("status"))) def on_track_change(player, e): #this function will run when the music is updated (paused, skipped, etc.) try: #see if the connection is still open ws.send('%s,%s,%s,%s' % (os.popen("playerctl metadata mpris:artUrl").read(), Playerctl.Player().get_title(), Playerctl.Player().get_artist(), Playerctl.Player().get_property("status"))) except WebSocketError: #if it's not open, stop the process player.stop() loop.quit() ws.close() player = Playerctl.Player() player.on('metadata', on_track_change) #listen for music updates loop = GLib.MainLoop() loop.run() #run the playerctl updater print("Disconnected from websocket") run(host='0.0.0.0', port=7000, server=GeventWebSocketServer) #run the server

Frontend

In this section, we create the webpage that is shown (and updated in real time) for the user.

Settings

Here we add color choosing and links that we use in html later.

#!/usr/bin/env python2 from bottle import route, run, request, get, post, static_file import gi gi.require_version('Playerctl', '1.0') from gi.repository import GLib, GObject, Playerctl from subprocess import Popen import sys import os Popen([sys.executable, './sendup.py']) #run the websocket script in the same folder (used to update info) ############ # SETTINGS ############ links = [['DevPy','http://DevPy.me'],['Reddit','http://Reddit.com'],['Google','http://Google.com']] #List of links to add to page theme = 'dark' #light or dark theme (sets colors) ############ # /SETTINGS ############ if theme == 'dark': bgc = '#222' fgc = 'rgba(255,255,255,0.6)' bw = 'gray' else: bgc = '#fff' fgc = '#111' bw = 'black' for x in range(len(links)): #create list of links links[x] = '<a class="db link no-underline underline-hover '+bw+'" href="'+links[x][1]+'">'+links[x][0]+'</a>' linkhtml = '''<div class="db center" style="width: 182px;"><div class="mt4 lh-copy">''' #add the links together for x in links: linkhtml += x linkhtml += '''</div></div>'''

HTML

We generate the initial look of the app. The artist, song, and image are initialized as well.

@get('/') # or @route('/login') def index(): player = Playerctl.Player() #grab a player try: #Check if a music app is open (spotify, audacious, vlc,...) title = player.get_title() artist = player.get_artist() img = os.popen("playerctl metadata mpris:artUrl").read() except: #if nothing is open just show blank music title = '' artist = '' img = '' return ''' <html> <head> <title>Start Page</title> <link rel="stylesheet" href="/static/tachyons.css" /> <style> /* Here we add the icons for play/pause/next/forward */ @font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: local('Material Icons'), local('MaterialIcons-Regular'), url(/static/mat.woff2) format('woff2'); } .material-icons { cursor: pointer; font-family: 'Material Icons'; font-weight: normal; font-style: normal; font-size: 30px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -webkit-font-feature-settings: 'liga'; -webkit-font-smoothing: antialiased; } .material-icons.md-light { color: rgba(0, 0, 0, 0.54); } .material-icons.md-dark { color: rgba(255, 255, 255, 1); } </style> </head> <body style="background-color:'''+bgc+'''"> <div style="position:relative; top: 35%%; transform: translateY(-50%%); "> <div class="mt4 db center black link" style="width: 178px;"> <img id="img" style="width:180px;height:178px;background-color:#222;"class="db ba b--black-10" src="%s"> <dl class="mt2 lh-copy"> <dt class="clip">Title</dt> <dd id="title" class="ml0 fw9" style="color:''' % (img)+fgc+'''">%s</dd> <dt class="clip">Artist</dt> <dd id="artist" class="ml0 gray">%s</dd> </dl> <div style="text-align:center;color:''' % (title, artist)+fgc+'''"> <a onclick="previous();" style="float:left;"><i class="material-icons md-'''+theme+'''">skip_previous</i></a> <a onclick="toggle();"><i id ="stateicon" style="width:32px;" class="material-icons md-'''+theme+'''">pause_arrow</i></a> <a onclick="next();" style="float:right;"><i class="material-icons md-'''+theme+'''">skip_next</i></a> </div> </div> ''' + linkhtml + # add the scripts below here

Scripts

Here we add some jquery scripts to talk to the server.

# attach this to the previous html code ''' </div> <script src="/static/jquery-3.1.1.min.js"></script> <script type="text/javascript"> var ws; ws = new WebSocket("ws://0.0.0.0:7000/websocket"); ws.onopen = function (evt) { ws.send("Connected"); }; ws.onclose = function (evt) { ws.send("Closed"); }; //in this function we listen for updates from the server and write them to the page. ws.onmessage = function (evt) { var array = evt.data.split(','); if (array.length == 4) { console.log(array); $("#img").attr("src", array[0]); $("#title").text(array[1]); $("#artist").text(array[2]); if (array[3] === 'Playing') { $('#stateicon').text('pause'); } else { $('#stateicon').text('play_arrow'); } ws.send("Updated"); } else { ws.send("OK.") } }; </script> <script> // when the buttons are pressed, post the server function next() { $.post('/next') return false; } function previous() { $.post('/previous') return false; } function toggle() { $.post('/toggle'); return false; } </script> </body> </html> '''

Post replies

Control the music when the client POSTs the server.

@post('/next') def next_song(): Playerctl.Player().next() @post('/previous') def next_song(): Playerctl.Player().previous() @post('/toggle') def next_song(): Playerctl.Player().play_pause() @post('/status') def is_playing(): return Playerctl.Player().get_property("status") @route('/static/<filename:path>') #This is for static files def send_static(filename): return static_file(filename, root=os.path.dirname(os.path.realpath(' '))) run(host='0.0.0.0', port=8080) #run the server

Finishing up

Now you should have a nice music controlling start page. You can make this show as the new tab on Chrome with this extension, and on Firefox with this one. Just set the url to 0.0.0.0:8080 .

Here I use my method to show a music visualizer on the desktop, plus my openbox setup.

Youtube video