Script to control XBMC from XMMS
#1
hi everyone,

first, let me say that the recent improvements such as the ipod-style nav have made it much easier to play music from xbmc even with the limitations of a remote.

still, i found myself wishing i was able to use a xmms (or winamp) interface from time to time, mainly for the quick search capability using the keyboard. so i wrote the python script below that sits in the background and makes a remote xbmc play whatever xmms plays currently, by translating local filenames into xbmc-visible files and sending commands to the web interface. i thought i'd share it with you.

it's definitely not perfect -- for example it can't seek or control volume, because these options don't exist in the web interface. but it handles play, pause, stop, and song changes, which is about 99% of what i need. it needs the pyxmms module (found here).
also, it can play a local file specified on the command line -- for that functionality it doesn't even need pyxmms. i *think* it needs python >2.2, i used python 2.3.4 for testing.

i'm sure the script could be enhanced to support winamp as well, maybe somebody who uses winamp cound do that.

enjoy, i hope this helps.

regards,
ovy

Quote:#!/usr/bin/python

# configuration parameters
host = "xbox"
port = 80

# if none or incorrect, you will be prompted
username = "xbox"
password = "yourpass"

# mapping of local path prefixes to xbmc share names ("bookmarks"). don't
# forget to add symlinks if you have any. the most precise mapping (longest
# match) will be preferred.
share_mappings = {
"/home/ovy/mp3" : "ursus/mp3",
"/home/mp3" : "ursus/mp3",
"/mp3" : "ursus/mp3",
"/home/ovy/sound/mp3" : "ursus/ovy/sound/mp3",
"/home/ovy/d" : "ursus/d",
}

# sleep intervals, in seconds
active_sleep = .1 # how often to check on xmms
stop_pause_sleep = .5 # ... when stopped or paused
idle_sleep = 5 # how much to sleep when xmms
# or xbmc are not responding
# the strings below are not config parameters, but you might need to change
# them if the html output of xbmc changes
xbmcroot = "http://%s:%s/" % (host, port)
musicroot = "default.asp?action=music"
playurl = "xbmccmds/xbmcform?command=play"
pauseurl = "xbmccmds/xbmcform?command=pause"
stopurl = "xbmccmds/xbmcform?command=stop"
# tells as how to find the link for an item in xbmc's html
findlinkregex = '<div class="filename"><a href="(.*?)"[^>]*?>%s</a></div>'

# actual code
import urllib
import re
import os
import sys
import time

class myopener(urllib.fancyurlopener, object):
def (self):
self.firsttimeprompt = true
super(myopener, self).()

def prompt_user_passwd(self, host, realm):
# if called multiple times, assume pass was incorrect
if username and password and self.firsttimeprompt:
self.firsttimeprompt = false
return username, password
else:
return super(myopener, self).prompt_user_passwd(host, realm)

opener = myopener()

class itemnotfound(exception): pass

def getpage(relativeurl):
global opener
firsterror = true
while true:
try:
return opener.open(xbmcroot + relativeurl).read()
except attributeerror: return none # bug in urllib
except ioerror, e:
if firsterror:
print "can't connect to xbmc: %s" % e.args[1][1]
print "going to sleep"
time.sleep(idle_sleep)
firsterror = false


def findlink(page, item):
finder = re.compile(findlinkregex % re.escape(item))

found = finder.findall(page)
if not found:
raise itemnotfound, "could not find in html page: %s" % item
link = found[0].replace("&", "&")
return link

def playxboxfile(share, relativepath):
page = getpage(musicroot)
page = getpage(findlink(page, share))

items = relativepath.split("/")

for item in items:
try:
page = getpage(findlink(page, item))
except itemnotfound,e: print e; return

def playlocalfile(path):
xboxpath = os.path.basename(path)
share = os.path.dirname(path)

while not share_mappings.has_key(share):
if not share or share == "/":
print "could not find share from %s" % path;
return
xboxpath = os.path.basename(share) + "/" + xboxpath
share = os.path.dirname(share)

xboxshare = share_mappings[share]
playxboxfile(xboxshare, xboxpath)

def xmmswatchloop():
import xmms
# do one connection to prompt for password and ensure xbmc up
getpage(musicroot)

play, pause, stop = range(3)

xbmccommands = [ playurl, pauseurl, stopurl ]
statemsgs = [ "play", "pause", "stop" ]

xmmsstate = play
xbmcstate = play

xbmcfile = none
while true:
if not xmms.is_running(): # retry loop
print "xmms doesn't seem to be running, going to sleep"
while not xmms.is_running(): time.sleep(idle_sleep)
print "connected to xmms"

if not xmms.is_playing(): xmmsstate = stop
elif xmms.is_paused(): xmmsstate = pause
else: xmmsstate = play

if xmmsstate != play:
# for play we postpone the state change
if xbmcstate != xmmsstate:
print statemsgs[xmmsstate]
getpage(xbmccommands[xmmsstate])
xbmcstate = xmmsstate
time.sleep(stop_pause_sleep)
continue

pos = xmms.control.get_playlist_pos();
xmmsfile = xmms.control.get_playlist_file(pos)

if not xmmsfile: continue # xmms may have died

if xmmsfile != xbmcfile:
print "playing: %s" % xmms.control.get_playlist_title(pos)
playlocalfile(xmmsfile)
xbmcfile = xmmsfile
# if we've just issued playfile no point in issuing play cmd again
# actually song changes sound really bad if we do :)
elif xbmcstate == stop:
getpage(playurl); print statemsgs[play]
elif xbmcstate == pause: # xbmc doesn't unpause on play
getpage(pauseurl); print statemsgs[play]

xbmcstate = play

time.sleep(active_sleep)

def main(argv=sys.argv):
if len(argv) != 2 or argv[1] == "--help":
print "usage: xbmcplay.py {--xmms | file }"
print "--xmms\ttell xbmc to play whatever xmms plays"
print "file\ttell xbmc to play this audio file"
elif argv[1] == "--xmms":
xmmswatchloop()
else:
print "playing: %s" % argv[1]
playlocalfile(argv[1])

if == "":
main()
Reply
#2
ok, found a problem when posting python identifiers starting with underscores. i don't really know how to fix it properly -- i've substituted the double underscores with uu, please do a replace before you run it. is there any way i can attach a file instead of pasting it in?

also, a small bug which causes it to not sleep when failing to contact xbmc. fixed below.

ovy

Quote:#!/usr/bin/python

# configuration parameters
host = "xbox"
port = 80

# if none or incorrect, you will be prompted
username = "xbox"
password = "yourpass"

# mapping of local path prefixes to xbmc share names ("bookmarks"). don't
# forget to add symlinks if you have any. the most precise mapping (longest
# match) will be preferred.
share_mappings = {
"/home/ovy/mp3" : "ursus/mp3",
"/home/mp3" : "ursus/mp3",
"/mp3" : "ursus/mp3",
"/home/ovy/sound/mp3" : "ursus/ovy/sound/mp3",
"/home/ovy/d" : "ursus/d",
}

# sleep intervals, in seconds
active_sleep = .1 # how often to check on xmms
stop_pause_sleep = .5 # ... when stopped or paused
idle_sleep = 5 # how much to sleep when xmms
# or xbmc are not responding
# the strings below are not config parameters, but you might need to change
# them if the html output of xbmc changes
xbmcroot = "http://%s:%s/" % (host, port)
musicroot = "default.asp?action=music"
playurl = "xbmccmds/xbmcform?command=play"
pauseurl = "xbmccmds/xbmcform?command=pause"
stopurl = "xbmccmds/xbmcform?command=stop"
# tells as how to find the link for an item in xbmc's html
findlinkregex = '<div class="filename"><a href="(.*?)"[^>]*?>%s</a></div>'

# actual code
import urllib
import re
import os
import sys
import time

class myopener(urllib.fancyurlopener, object):
def uuinituu(self):
self.firsttimeprompt = true
super(myopener, self).uuinituu()

def prompt_user_passwd(self, host, realm):
# if called multiple times, assume pass was incorrect
if username and password and self.firsttimeprompt:
self.firsttimeprompt = false
return username, password
else:
return super(myopener, self).prompt_user_passwd(host, realm)

opener = myopener()

class itemnotfound(exception): pass

def getpage(relativeurl):
global opener
firsterror = true
while true:
try:
return opener.open(xbmcroot + relativeurl).read()
except attributeerror: return none # bug in urllib
except ioerror, e:
if firsterror:
print "can't connect to xbmc: %s" % e.args[1][1]
print "going to sleep"
firsterror = false
time.sleep(idle_sleep)


def findlink(page, item):
finder = re.compile(findlinkregex % re.escape(item))

found = finder.findall(page)
if not found:
raise itemnotfound, "could not find in html page: %s" % item
link = found[0].replace("&", "&")
return link

def playxboxfile(share, relativepath):
page = getpage(musicroot)
page = getpage(findlink(page, share))

items = relativepath.split("/")

for item in items:
try:
page = getpage(findlink(page, item))
except itemnotfound,e: print e; return

def playlocalfile(path):
xboxpath = os.path.basename(path)
share = os.path.dirname(path)

while not share_mappings.has_key(share):
if not share or share == "/":
print "could not find share from %s" % path;
return
xboxpath = os.path.basename(share) + "/" + xboxpath
share = os.path.dirname(share)

xboxshare = share_mappings[share]
playxboxfile(xboxshare, xboxpath)

def xmmswatchloop():
import xmms
# do one connection to prompt for password and ensure xbmc up
getpage(musicroot)

play, pause, stop = range(3)

xbmccommands = [ playurl, pauseurl, stopurl ]
statemsgs = [ "play", "pause", "stop" ]

xmmsstate = play
xbmcstate = play

xbmcfile = none
while true:
if not xmms.is_running(): # retry loop
print "xmms doesn't seem to be running, going to sleep"
while not xmms.is_running(): time.sleep(idle_sleep)
print "connected to xmms"

if not xmms.is_playing(): xmmsstate = stop
elif xmms.is_paused(): xmmsstate = pause
else: xmmsstate = play

if xmmsstate != play:
# for play we postpone the state change
if xbmcstate != xmmsstate:
print statemsgs[xmmsstate]
getpage(xbmccommands[xmmsstate])
xbmcstate = xmmsstate
time.sleep(stop_pause_sleep)
continue

pos = xmms.control.get_playlist_pos();
xmmsfile = xmms.control.get_playlist_file(pos)

if not xmmsfile: continue # xmms may have died

if xmmsfile != xbmcfile:
print "playing: %s" % xmms.control.get_playlist_title(pos)
playlocalfile(xmmsfile)
xbmcfile = xmmsfile
# if we've just issued playfile no point in issuing play cmd again
# actually song changes sound really bad if we do :)
elif xbmcstate == stop:
getpage(playurl); print statemsgs[play]
elif xbmcstate == pause: # xbmc doesn't unpause on play
getpage(pauseurl); print statemsgs[play]

xbmcstate = play

time.sleep(active_sleep)

def main(argv=sys.argv):
if len(argv) != 2 or argv[1] == "--help":
print "usage: xbmcplay.py {--xmms | file }"
print "--xmms\ttell xbmc to play whatever xmms plays"
print "file\ttell xbmc to play this audio file"
elif argv[1] == "--xmms":
xmmswatchloop()
else:
print "playing: %s" % argv[1]
playlocalfile(argv[1])

if uunameuu== "uumainuu":
main()
Reply

Logout Mark Read Team Forum Stats Members Help
Script to control XBMC from XMMS0