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()