XSPF Tag Playlist Generator
Once again I am working with XSPF playlists to organize music. I have made two new Python 3 scripts to handle tagging, so that files can be tagged (with multiple tags if needed) and then playlists can be generated based on this data. A JSON file serves as the database for the tags and is the link between the two scripts.
First script, for tagging files and manipulating the JSON database:
#!/usr/bin/python3 import os import json class TagDB(object): def __init__(self, db_file): self._db_file = db_file try: db_fh = open(db_file, "r") self._db = json.load(db_fh) db_fh.close() except: self._db = dict() def apply(self, filename, tags): local = tags.copy() if filename in self._db: local.extend(self._db[filename]) self._db[filename] = list(dict.fromkeys(local)) # Remove duplicates! def delete(self, filename, tags): deleted = list() if filename in self._db: for t in tags: try: self._db[filename].remove(t) deleted.append(t) except: pass return deleted def retrieve(self, filename): if filename in self._db: return self._db[filename] else: return list() def all_tags(self): tags = dict() for v in self._db.values(): for t in v: tags[t] = True return list(tags.keys()) def save(self): untagged = list() for filename in self._db: if len(self._db[filename]) == 0: untagged.append(filename) for filename in untagged: del self._db[filename] db_fh = open(self._db_file, "w") json.dump(self._db, db_fh) db_fh.close() def usage(progname): print("%s <options> <files>" % (progname)) print("-h Help!") print("-r Recursively go through directories.") print("-l List previously used tags.") print("-d Delete tag instead of applying.") print("-f DB Database file to use.") print("-t TAG Tag to apply (or delete).") print("") print("The -t can be specified multiple times for multiple tags.") print("If -t is omitted, current tags are listed for found files.") print("") if __name__ == "__main__": import sys import getopt try: opts, args = getopt.getopt(sys.argv[1:], "hrldf:t:") except getopt.GetoptError as err: print(err) usage(sys.argv[0]) sys.exit(1) recursive = False list_tags = False delete = False database = None tags = list() for o, a in opts: if o == "-h": usage(sys.argv[0]) sys.exit(0) elif o == "-r": recursive = True elif o == "-l": list_tags = True elif o == "-d": delete = True elif o == "-f": database = a elif o == "-t": tags.append(a) if not database: print("Please specify the database file!") usage(sys.argv[0]) sys.exit(1) db = TagDB(database) if list_tags: print("Previously used tags from database:") for t in db.all_tags(): print(" '%s'" % t) print("") filenames = list() for a in args: if os.path.isfile(a): filenames.append(os.path.abspath(a)) if os.path.isdir(a) and recursive: for root, dirs, files in os.walk(a): for name in files: filenames.append(os.path.abspath(os.path.join(root, name))) for filename in filenames: if len(tags) == 0: actual = db.retrieve(filename) if len(actual) > 0: print(filename, "===", actual) else: if delete: actual = db.delete(filename, tags) if len(actual) > 0: print(filename, "---", actual) else: db.apply(filename, tags) print(filename, "+++", tags) if len(tags) > 0: db.save()
Second script, for generating the XSPF playlist from the JSON database:
#!/usr/bin/python3 import json import sys from urllib.parse import quote if len(sys.argv) != 2: print("Usage: %s <database>" % (sys.argv[0])) sys.exit(1) fh = open(sys.argv[1], "r") db = json.load(fh) fh.close() playlist = dict() for filename in db: for tag in db[filename]: if not tag in playlist: playlist[tag] = list() playlist[tag].append(filename) for tag in playlist: fh = open("%s.xspf" % (tag), "w") fh.write('<?xml version="1.0" encoding="UTF-8"?>\n') fh.write('<playlist version="1" xmlns="http://xspf.org/ns/0/">\n') fh.write(' <title>%s</title>\n' % (tag)) fh.write(' <trackList>\n') for filename in sorted(playlist[tag]): fh.write(' <track>\n') fh.write(' <location>file://%s</location>\n' % (quote(filename))) fh.write(' </track>\n') fh.write(' </trackList>\n') fh.write('</playlist>\n') fh.close()