#!/usr/bin/env python3 from __future__ import print_function import sys from itertools import groupby from dateutil.parser import parse as parseISO8601 import os import notify2 import json import argparse import operator import fnmatch from pprint import pprint from mpd import MPDClient import configparser client = MPDClient() config = configparser.ConfigParser() config.sections() config.read(os.getenv('HOME')+'/.config/clerk/helper_config') change_db = (config['updater']['change_db']) separator = (config['global']['separator']) os.environ['separator'] = str(" "+separator+" ") mpd_host = 'localhost' mpd_port = '6600' mpd_pass = '' if 'MPD_HOST' in os.environ: mpd_connection = os.environ['MPD_HOST'].split('@') if len(mpd_connection) == 1: mpd_host = mpd_connection[0] elif len(mpd_connection) == 2: mpd_host = mpd_connection[1] mpd_pass = mpd_connection[0] else: print('Unable to parse MPD_HOST, using defaults') if 'MPD_PORT' in os.environ: mpd_port = os.environ['MPD_PORT'] client.connect(mpd_host, mpd_port) if mpd_pass: client.password(mpd_pass) def update(args): db = (client.stats()) new_db = db.get('db_update') if change_db == new_db: if (os.path.isfile(os.getenv('HOME')+'/.config/clerk/latest.cache')): quit() else: notify2.init('Clerk') n = notify2.Notification("Clerk", "Updating Cache Files") n.show() createCache(args) else: notify2.init('Clerk') n = notify2.Notification("Clerk", "Updating Cache Files") n.show() createCache(args) config['updater']['change_db'] = str(new_db) with open(os.getenv('HOME')+'/.config/clerk/helper_config', 'w') as configfile: config.write(configfile) def reduceToFstElm(maybeList): return maybeList[0] if isinstance(maybeList, list) else maybeList # sort albums by mtime # thanks to kuyatzu for this. def createRecentList(allTracks): def gp(t): return (t['album'], t['albumartist'], t['date']) for t in allTracks: t['date'] = reduceToFstElm(t['date']) t['albumartist'] = reduceToFstElm(t['albumartist']) t['cto'] = parseISO8601(t['last-modified']) # cto = comparable time object accu = [] for album, tracksOfAlbum in groupby(allTracks, key=gp): accu.append(sorted(tracksOfAlbum, key=lambda t: t['cto'])[-1]) return sorted(accu, key=lambda t: t['cto'], reverse=True) def createAlbumsList(tracks): ks = ['date', 'artist', 'albumartist', 'album', 'track', 'title'] for t in tracks: t.update([(k, reduceToFstElm(v)) for (k, v) in t.items() if k in ks]) return tracks def createCache(args): # create latest.cache alist=client.search('filename', " ") blist=createRecentList(alist) with open(os.getenv('HOME')+'/.config/clerk/latest.cache', "w") as cache_file: for album in blist: cache_file.write(album['albumartist']+"\t("+album['date']+") - "+album['album']+'\n') # create albums.cache temp_albums=[] blist=createAlbumsList(alist) albumlist=createRecentList(blist) for album in albumlist: temp_albums.append(album['albumartist']+"\t("+album['date']+") - "+album['album']) final_albumlist=set(temp_albums) blub=sorted(final_albumlist) with open(os.getenv('HOME')+'/.config/clerk/albums.cache', "w") as cache_file: for album in blub: cache_file.write(album+'\n') # create tracks.cache tracklist=[] for track in alist: taglist = ['date', 'artist', 'albumartist', 'album', 'track', 'title', 'file'] track.update([(k, reduceToFstElm(v)) for (k, v) in track.items() if k in taglist]) tracklist.append(track['track']+"\t"+track['title']+"\tby "+track['artist']+" on "+track['album']+" ("+track['date']+")\t"+track['file']) # with open(os.getenv('HOME')+'/.config/clerk/tracks.cache', "w") as cache_file: for track in tracklist: cache_file.write(track+'\n') def track_sort(track_num): if track_num == '': return 0 else: return int(track_num.split('/')[0]) def loadjson(fname): with open(fname) as data_file: data = json.load(data_file) return data def extract_key(entry): return entry["albumartist"] + entry["album"] + entry["date"] def hashdata(jdata): hashmap = {} for i in range(len(jdata)): key = extract_key(jdata[i]) hashmap[key] = i return hashmap def has_entry(fastlist, albumartist, album, date): key = albumartist+album+date if key in fastlist[1]: return True else: return False def get_entry(fastlist, albumartist, album, date): return fastlist[0][fastlist[1][albumartist+album+date]] def load_fastlist(fname): jdata = loadjson(fname) hdata = hashdata(jdata) return (jdata, hdata) def save_fastlist(fname, fastlist): with open(fname, "w") as data_file: json.dump(fastlist[0], data_file, ensure_ascii=False) def append_entry(fastlist, entry): index = len(fastlist[0]) fastlist[0].append(entry) fastlist[1][extract_key(entry)] = index def prune_fastlist(fastlist, mpdcachefile): mpdlist = loadjson(mpdcachefile) data = fastlist[0] new = [] for e in mpdlist: key = (e["albumartist"], e["album"], e["date"]) if has_entry(fastlist, *key): entry = get_entry(fastlist, *key) new.append(entry) newhdata = hashdata(new) return (new, newhdata) def rateAlbum(args): fastlist = load_fastlist(os.getenv('HOME')+'/.config/clerk/albumratings.json') try: entry = get_entry(fastlist, args.artist, args.album, args.date) entry["rating"] = args.rating save_fastlist(os.getenv('HOME')+'/.config/clerk/albumratings.json', fastlist) uri = client.find('albumartist', args.artist, 'album', args.album, 'date', args.date, 'track', os.getenv('track'), 'disc', os.getenv('disc')) for i in uri: client.sticker_set("song", i['file'], "albumrating", args.rating) except KeyError: entry = {'albumartist': args.artist, 'album': args.album, 'date': args.date, 'disc': os.getenv('disc'), 'track': os.getenv('track'), 'rating': args.rating} append_entry(fastlist, entry) save_fastlist(os.getenv('HOME')+'/.config/clerk/albumratings.json', fastlist) uri = client.find('artist', args.artist, 'album', args.album, 'date', args.date, 'track', os.getenv('track'), 'disc', os.getenv('disc')) for i in uri: client.sticker_set("song", i['file'], "albumrating", args.rating) def loadjsonTrack(fname): with open(fname) as data_file: data = json.load(data_file) return data def extract_keyTrack(entry): return entry["artist"] + entry["album"] + entry["track"] + entry["title"] def hashdataTrack(jdata): hashmap = {} for i in range(len(jdata)): key = extract_keyTrack(jdata[i]) hashmap[key] = i return hashmap def has_entryTrack(fastlist, artist, album, track, title): key = artist+album+track+title if key in fastlist[1]: return True else: return False def get_entryTrack(fastlist, artist, album, track, title): return fastlist[0][fastlist[1][artist+album+track+title]] def load_fastlistTrack(fname): jdata = loadjsonTrack(fname) hdata = hashdataTrack(jdata) return (jdata, hdata) def save_fastlistTrack(fname, fastlist): with open(fname, "w") as data_file: json.dump(fastlist[0], data_file, ensure_ascii=False) def append_entryTrack(fastlist, entry): index = len(fastlist[0]) fastlist[0].append(entry) fastlist[1][extract_keyTrack(entry)] = index def prune_fastlistTrack(fastlist, mpdcachefile): mpdlist = loadjsonTrack(mpdcachefile) data = fastlist[0] new = [] for e in mpdlist: key = (e["artist"], e["album"], e["track"], e["title"]) if has_entryTrack(fastlist, *key): entry = get_entryTrack(fastlist, *key) new.append(entry) newhdata = hashdataTrack(new) return (new, newhdata) def rateTrack(args): fastlist = load_fastlistTrack(os.getenv('HOME')+'/.config/clerk/trackratings.json') try: entry = get_entryTrack(fastlist, args.artist, args.album, args.track, args.title) entry["rating"] = args.rating save_fastlistTrack(os.getenv('HOME')+'/.config/clerk/trackratings.json', fastlist) uri = client.find('artist', args.artist, 'album', args.album, 'track', args.track, 'title', args.title) for i in uri: client.sticker_set("song", i['file'], "rating", args.rating) except KeyError: entry = {'artist': args.artist, 'album': args.album, 'track': args.track, 'title': args.title, 'rating': args.rating} append_entryTrack(fastlist, entry) save_fastlistTrack(os.getenv('HOME')+'/.config/clerk/trackratings.json', fastlist) uri = client.find('artist', args.artist, 'album', args.album, 'track', args.track, 'title', args.title) for i in uri: client.sticker_set("song", i['file'], "rating", args.rating) def cleanRatings(args): fastlist = load_fastlist(os.getenv('HOME')+'/.config/clerk/albumratings.json') prunedlist = prune_fastlist(fastlist, os.getenv('HOME')+'/.config/clerk/albums.cache.json') save_fastlist(os.getenv('HOME')+'/.config/clerk/albumratings.json', prunedlist) fastlist = load_fastlistTrack(os.getenv('HOME')+'/.config/clerk/trackratings.json') prunedlist = prune_fastlistTrack(fastlist, os.getenv('HOME')+'/.config/clerk/tracks.cache.json') save_fastlistTrack(os.getenv('HOME')+'/.config/clerk/trackratings.json', prunedlist) def importTrackRatings(args): tracklist = client.sticker_find('song', "", 'rating') rated_tracks = [] for i in tracklist: rating_temp = i['sticker'] rating = rating_temp.split('=')[1] for x in client.find('file', i['file']): artist = x['artist'] title = x['title'] album = x['album'] if isinstance(x['track'], list): track = x['track'][0] else: track = x['track'] entry = {'artist': artist, 'album': album, 'track': track, 'title': title, 'rating': rating} rated_tracks.append(entry) with open(os.getenv('HOME')+'/.config/clerk/trackratings.json', 'w') as ratingfile: json.dump(rated_tracks, ratingfile) def importAlbumRatings(args): ratelist = client.sticker_find('song', "", 'albumrating') rated_tracks = [] for i in ratelist: rating_temp = i['sticker'] rating = rating_temp.split('=')[1] for x in client.find('file', i['file']): artist = x['albumartist'] album = x['album'] date = x['date'] if isinstance(x['track'], list): track = x['track'][0] else: track = x['track'] if 'disc' in x: if isinstance(x['disc'], list): disc = x['disc'][0] else: disc = x['disc'] else: disc = "" entry = {'albumartist': artist, 'track': track, 'album': album, 'date': date, 'disc': disc, 'rating': rating} rated_tracks.append(entry) with open(os.getenv('HOME')+'/.config/clerk/albumratings.json', 'w') as ratingfile: json.dump(rated_tracks, ratingfile) def sendStickers(args): with open(os.getenv('HOME')+'/.config/clerk/trackratings.json') as cache_file: content = json.load(cache_file) for track in content: uri = client.find('artist', track['artist'], 'album', track['album'], 'title', track['title'], 'track', track['track']) rating = track['rating'] for x in uri: client.sticker_set('song', x['file'], 'rating', rating) print("Imported Rating of "+rating+" for file "+x['file']) with open(os.getenv('HOME')+'/.config/clerk/albumratings.json') as cache_file: content = json.load(cache_file) for album in content: uri = client.find('albumartist', album['albumartist'], 'album', album['album'], 'disc', album['disc'], 'date', album['date'], 'track', album['track']) rating = album['rating'] for x in uri: client.sticker_set('song', x['file'], 'albumrating', rating) print("Imported Rating of "+rating+" for album "+album['albumartist']+" - "+album['album']) def getAlbumRatings(args): ratings = client.sticker_find('song', '', 'albumrating') albumlist = [] for x in ratings: rating = x['sticker'] albums = client.find('file', x['file']) for album in albums: entry = {'albumartist': album['albumartist'], 'date': album['date'], 'album': album['album'], 'track': album['track'], 'rating': rating} print(entry['albumartist']+os.getenv('separator')+entry['date']+os.getenv('separator')+entry['album']+os.getenv('separator')+entry['rating']) def readComments(args): args = vars(args) comments = (client.readcomments(sys.stdin.read()[:-1])) for key, value in sorted(comments.items()): print('%s : %s' % (key, value)) def prioSong(args): for line in sys.stdin.read().splitlines(): client.prio(255, line) def savetoPlaylist(args): for line in sys.stdin: if line.strip(): line = line.strip() client.playlistadd("clerk", line) # print(line, end="") def getArtistAlbums(args): albums=client.find('artist', args.artist) albumlist=[] for album in albums: albumlist.append(album['date']+os.getenv('separator')+album['album']) uni=set(albumlist) result = list(uni) sorted_results = sorted(result) for x in sorted_results: print("(Album) "+x) def getArtistTracks(args): tracks=client.find('artist', args.artist) tracklist=[] for track in tracks: entry = {'track': track['track'], 'title': track['title']} tracklist.append(entry) for x in tracklist: print("(Song) "+x['track']+os.getenv('separator')+x['title']) # create commandline arguments parser = argparse.ArgumentParser(prog='clerk_helper', description='Companion script for clerk') subparsers = parser.add_subparsers() parser_prio = subparsers.add_parser('prio', help="prioritize song") parser_prio.set_defaults(call=prioSong) parser_update = subparsers.add_parser('update', help="update cache files") parser_update.set_defaults(call=update) parser_readcomments = subparsers.add_parser('readcomments', help="show all tags of current song") parser_readcomments.set_defaults(call=readComments) parser_saveto = subparsers.add_parser('saveto', help="save stdin to playlist \"clerk\"") parser_saveto.set_defaults(call=savetoPlaylist) parser_createcache = subparsers.add_parser('createcache', help="create track cache for clerk") parser_createcache.set_defaults(call=createCache) parser_ratealbum = subparsers.add_parser('ratealbum', help="rate current Album") parser_ratealbum.add_argument('artist', action="store") parser_ratealbum.add_argument('album', action="store") parser_ratealbum.add_argument('date', action="store") parser_ratealbum.add_argument('rating', action="store") parser_ratealbum.set_defaults(call=rateAlbum) parser_ratetrack = subparsers.add_parser('ratetrack', help="rate current Track") parser_ratetrack.add_argument('artist', action="store") parser_ratetrack.add_argument('album', action="store") parser_ratetrack.add_argument('track', action="store") parser_ratetrack.add_argument('title', action="store") parser_ratetrack.add_argument('rating', action="store") parser_ratetrack.set_defaults(call=rateTrack) parser_cleanratings = subparsers.add_parser('cleanratings', help="Clean up Ratings file.") parser_cleanratings.set_defaults(call=cleanRatings) parser_importtrackratings = subparsers.add_parser('importtrackratings', help="Import track ratings from mpd stickers") parser_importtrackratings.set_defaults(call=importTrackRatings) parser_importalbumratings = subparsers.add_parser('importalbumratings', help="Import album ratings from mpd stickers") parser_importalbumratings.set_defaults(call=importAlbumRatings) parser_sendstickers = subparsers.add_parser('sendstickers', help="Send clerk ratings to mpd") parser_sendstickers.set_defaults(call=sendStickers) parser_getalbumratings = subparsers.add_parser('getalbumratings', help="Print Album Ratings") parser_getalbumratings.set_defaults(call=getAlbumRatings) parser_getartistalbums = subparsers.add_parser('getartistalbums', help="Print all albums by Artist") parser_getartistalbums.add_argument('artist', action="store") parser_getartistalbums.set_defaults(call=getArtistAlbums) parser_getartisttracks = subparsers.add_parser('getartisttracks', help="Print all tracks by Artist") parser_getartisttracks.add_argument('artist', action="store") parser_getartisttracks.set_defaults(call=getArtistTracks) # parse arguments (thanks jat) args = parser.parse_args() try: args.call(args) except AttributeError: print("No arguments given. Try clerk_helper -h")