#!/usr/bin/env python3 from __future__ import print_function import sys 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/tracks.cache.json')): 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 lookup(key, fields): artistlist = client.list(key) for artist in artistlist: for element in client.find(key, artist): entry = {} for field in fields: if field in element: if isinstance(element[field], list): entry[field] = element[field][0] else: entry[field] = element[field] else: entry[field] = '' yield entry def createCache(args): # track cache creation key = 'artist' fields = ('artist', 'track', 'title', 'album', 'disc') all_tracks = list({repr(t): t for t in lookup(key, fields)}.values()) with open(os.getenv('HOME')+'/.config/clerk/tracks.cache.json', "w") as cache_file: json.dump(all_tracks, cache_file) # album cache creation with only the youngest track modified key = 'albumartist' fields = ('albumartist', 'date', 'album', 'last-modified') album_cache = {} for entry in lookup(key, fields): # key = repr(entry) # cant use repr in this case because last-modified could differ for # songs from the same album and we want these to clash. key = entry['albumartist'] + entry['album'] if key not in album_cache: album_cache[key] = entry else: if entry['last-modified'] > album_cache[key]['last-modified']: album_cache[key] = entry with open(os.getenv('HOME')+'/.config/clerk/albums.cache.json', "w") as cache_file: json.dump([entry for entry in album_cache.values()], cache_file) def track_sort(track_num): if track_num == '': return 0 else: return int(track_num.split('/')[0]) def getTracks(args): with open(os.getenv('HOME')+'/.config/clerk/tracks.cache.json') as cache_file: content = json.load(cache_file) sorted_content = sorted(content, key=lambda x: (x['artist'], x['album'], x['disc'], track_sort(x['track']))) print('\n'.join([os.getenv('separator').join([entry['artist'], entry['track'], entry['title'], entry['album']]) for entry in sorted_content])) def getAlbums(args): with open(os.getenv('HOME')+'/.config/clerk/albums.cache.json') as cache_file: content = json.load(cache_file) sorted_content = sorted(content, key=lambda x: (x['albumartist'], x['date'], x['album'])) print('\n'.join([os.getenv('separator').join([entry['albumartist'], entry['date'], entry['album']]) for entry in sorted_content])) def getLatest(args): with open(os.getenv('HOME')+'/.config/clerk/albums.cache.json') as cache_file: content = json.load(cache_file) sorted_content = sorted(content, key=lambda x: (x['last-modified']), reverse=True) print('\n'.join([os.getenv('separator').join([entry['date'], entry['albumartist'], entry['album'], entry['last-modified']]) for entry in sorted_content])) 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')) print(uri) 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')) print(uri) 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) print(uri) 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) print(uri) 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): list = client.sticker_find('song', "", 'rating') rated_tracks = [] for i in list: rating_temp = i['sticker'] rating = rating_temp.split('=')[1] for x in client.find('file', i['file']): artist = x['artist'] album = x['album'] track = x['track'] title = x['title'] 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, list): track = x['track'][0] else: track = x['track'] if 'disc' in x: 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_gettracks = subparsers.add_parser('getTracks', help="get all tracks from track cache for clerk") parser_gettracks.set_defaults(call=getTracks) parser_getalbums = subparsers.add_parser('getAlbums', help="get all albums from album cache for clerk") parser_getalbums.set_defaults(call=getAlbums) parser_getlatest = subparsers.add_parser('getLatest', help="get all albums from album cache for clerk, sorted by mtime") parser_getlatest.set_defaults(call=getLatest) 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")