#!/usr/bin/env python ## ## LoadSimilar v0.2 -- Sean McLean, 2007, Licensed under GPLv2 ## ################################################################################################## # USER-DEFINABLE PARAMETERS # this variable defines the maximum number of files that can be added at once addlimit = '300' # when set to True, various debugging crap is writtin to /tmp/loadsimilar-debug.txt debug = False # initial state of Random Order option randommode = False # initial state of Clear Playlist option clearfirst = True ################################################################################################## import os, sys, signal, urllib, math, random # normal commands commands = ["By Artist", "By Album", "By Title", "By Genre", "By Year", "By Score", "By Playcount"] # toggle options options = ["Random Order", "Clear Playlist"] # write a debug message if debugging is enabled def debugmsg(dbgstring): global debug if debug: global dbgout print >> dbgout, dbgstring dbgout.flush() # prepare the context menu items def initmenu(): # add commands for command in commands: os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "'+command+'"') # add toggles at apropriate state option_values = [randommode, clearfirst] for i in range(0,len(options)): if option_values[i]: os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[X] '+options[i]+'"') else: os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[ ] '+options[i]+'"') # delete the context menu items def cleanmenu(signum, frame): # remove commands for command in commands: os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "'+command+'"') # remove toggles, knowing their appropriate state option_values = [randommode, clearfirst] for i in range(0,len(options)): if option_values[i]: os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[X] '+options[i]+'"') else: os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[ ] '+options[i]+'"') # load a list of tracks which is returned by the given query def load_tracks_for_query(query): debugmsg(query) qout = os.popen('dcop amarok collection query "'+query+'"').readlines() kurllist = [] for url in qout: if url: if os.access(url[1:],os.F_OK): # normal local files can be (should be) found with the url from the db kurllist.append('"file://'+urllib.quote(url[1:].strip())+'"') else: # dynamic collection files, however, cannot be found in this way kurllist.append(find_dynamic_url(url)) if randommode: random.shuffle(kurllist) if clearfirst: os.system('dcop amarok playlist clearPlaylist') if len(kurllist): cmd = 'dcop amarok playlist addMediaList \( ' + ' '.join(kurllist) + ' \)' os.system(cmd) return len(kurllist) # make a KURL for a tags.url field known to represent a f ile on a dynamic collection def find_dynamic_url(url): query = 'select deviceid from tags where url=\'' + sanitize_string(url).strip() + '\'' qout = os.popen('dcop amarok collection query "'+query+'"').readlines() # debugmsg(query + ' = ' + qout[0]) # if a deviceid was returned if not qout[0]: os.system('dcop amarok playlist popupMessage "LoadSimilar could not deteremine the deviceid for a requested track"') else: devicemount = os.popen('dcop amarok collection query "select lastmountpoint from devices where id='+qout[0].strip()+'"').readlines() if not devicemount[0]: os.system('dcop amarok playlist popupMessage "LoadSimilar could not deteremine the dynamic path for a requested track"') else: return 'file://' + urllib.quote( devicemount[0].strip()+url[1:].strip() ) # load tags for a given url def load_tags_for_url(url): cmd = 'dcop amarok collection query "select * from tags where url like \'%'+sanitize_string(url)+'%\'"' debugmsg(cmd) qout = os.popen(cmd).readlines() return qout # sanitize a string; escape quotes def sanitize_string(string): return string.replace('\'','\\\'').replace('"','\\\"') # report that the file couldnt be found in the collection def tags_not_found(kurl): os.system('dcop amarok playlist popupMessage "LoadSimilar could not find the track you selected in your collection. '+ 'You may only use LoadSimilar for tracks that are within your collection (except for By Score and By Playcount) . '+ 'The track you selected was: '+sanitize_string(kurl)+'"') # open debugging output if debugging enabled if debug: dbgout = file('/tmp/loadsimilar-debug.txt','w+') debugmsg('Init') # set up menu context entries initmenu() # trap death for killing context menu entries signal.signal(signal.SIGTERM, cleanmenu) # main loop while True: # read amarok input stdinbuf = sys.stdin.readline() debugmsg('STDIN: '+stdinbuf) data = stdinbuf.split() # if its a context menu click on one of our entries if data[0] == 'customMenuClicked:' and data[1] == 'LoadSimilar': # debugmsg('/'.join(data)) # Random mode toggle if "Random" in data: debugmsg("Random in data") if randommode: debugmsg("Enable") randommode = False os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[X] Random Order"') os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[ ] Random Order"') else: randommode = True os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[ ] Random Order"') os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[X] Random Order"') # Clear first toggle if "Clear" in data: if clearfirst: clearfirst = False os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[X] Clear Playlist"') os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[ ] Clear Playlist"') else: clearfirst = True os.system('dcop amarok script removeCustomMenuItem "LoadSimilar" "[ ] Clear Playlist"') os.system('dcop amarok script addCustomMenuItem "LoadSimilar" "[X] Clear Playlist"') # By __ selected (otherwise file:// wouldnt be in 4.. ugly) if data[4][:7] == 'file://': os.system('dcop amarok playlist shortStatusMessage "LoadSimilar by '+data[3]+' running..."') # reset addcount and tags addcount = 0 # make a value which can be used to query the tags.url field out of this KURL. # this takes the kurl, chops off the file:///, decodes it, then takes the last directory and filename -- # this is not perfect (there is no perfect solution) but it should work 99% of the time url = os.sep.join(urllib.unquote(data[4][7:]).split(os.sep)[-2:]) if data[3] == 'Artist': tags = load_tags_for_url(url) if len(tags) > 1: addcount = load_tracks_for_query('select url from tags '+ 'inner join album on tags.album = album.id '+ 'where artist='+str(int(tags[5]))+' order by album.name, tags.track limit 0,'+addlimit) else: tags_not_found(data[4]) if data[3] == 'Album': tags = load_tags_for_url(url) if len(tags) > 1: addcount = load_tracks_for_query('select url from tags where album='+str(int(tags[4]))+' order by tags.track limit 0,'+addlimit) else: tags_not_found(data[4]) if data[3] == 'Title': tags = load_tags_for_url(url) if len(tags) > 1: addcount = load_tracks_for_query('select url from tags where title=\''+sanitize_string(tags[8].rstrip())+'\' order by tags.track limit 0,'+addlimit) else: tags_not_found(data[4]) if data[3] == 'Genre': tags = load_tags_for_url(url) if len(tags) > 1: addcount = load_tracks_for_query('select url from tags '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where genre='+str(int(tags[7]))+' order by artist.name, album.name, tags.track limit 0,'+addlimit) else: tags_not_found(data[4]) if data[3] == 'Year': tags = load_tags_for_url(url) if len(tags) > 1: addcount = load_tracks_for_query('select url from tags '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where year='+str(int(tags[9]))+' order by artist.name, album.name, tags.track limit 0,'+addlimit) else: tags_not_found(data[4]) if data[3] == 'Score': cmd = 'dcop amarok collection query "select percentage from statistics where url=\'' + sanitize_string(url) + '\'"' debugmsg(cmd) score = os.popen(cmd).readline().strip() if score == '': addcount = load_tracks_for_query('select tags.url from tags '+ 'left join statistics on statistics.url = tags.url '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where statistics.percentage is null order by artist.name, album.name, tags.track limit 0,'+addlimit) else: addcount = load_tracks_for_query('select statistics.url from statistics '+ 'inner join tags on statistics.url = tags.url '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where percentage='+score+' order by artist.name, album.name, tags.track limit 0,'+addlimit) if data[3] == 'Playcount': cmd = 'dcop amarok collection query "select playcounter from statistics where url=\'' + sanitize_string(url) + '\'"' debugmsg(cmd) playcount = os.popen(cmd).readline().strip() if playcount == '': addcount = load_tracks_for_query('select tags.url from tags '+ 'left join statistics on statistics.url = tags.url '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where statistics.playcounter is null order by artist.name, album.name, tags.track limit 0,'+addlimit) else: addcount = load_tracks_for_query('select statistics.url from statistics '+ 'inner join tags on statistics.url = tags.url '+ 'inner join album on tags.album = album.id '+ 'inner join artist on tags.artist = artist.id '+ 'where playcounter='+str(int(playcount))+' order by artist.name, album.name, tags.track limit 0,'+addlimit) os.system('dcop amarok playlist shortStatusMessage "LoadSimilar By '+data[3]+': Added '+str(addcount)+' tracks"')