A Python 3 script that exports all of your Spotify playlists.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

371 lines
11 KiB

#!/usr/bin/python
import xml.etree.ElementTree as ET
class XspfBase(object):
NS = "http://xspf.org/ns/0/"
def _addAttributesToXml(self, parent, attrs):
for attr in attrs:
value = getattr(self, attr)
if value:
el = ET.SubElement(parent, "{{{0}}}{1}".format(self.NS, attr))
el.text = value
def _addDictionaryElements(self, parent, name, values):
# Sort keys so we have a stable order of items for testing.
# Alternative would be SortedDict, but is >=2.7
for k in sorted(values.keys()):
el = ET.SubElement(parent, "{{{0}}}{1}".format(self.NS, name))
el.set("rel", k)
el.text = values[k]
# Avoid namespace prefixes, VLC doesn't like it
if hasattr(ET, 'register_namespace'):
ET.register_namespace('', XspfBase.NS)
# in-place prettyprint formatter
# From http://effbot.org/zone/element-lib.htm
def indent(elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
class Xspf(XspfBase):
def __init__(self, obj={}, **kwargs):
self.version = "1"
self._title = ""
self._creator = ""
self._info = ""
self._annotation = ""
self._location = ""
self._identifier = ""
self._image = ""
self._date = ""
self._license = ""
self._attributions = []
self._link = {}
self._meta = {}
self._trackList = []
if len(obj):
if "playlist" in obj:
obj = obj["playlist"]
for k, v in list(obj.items()):
setattr(self, k, v)
if len(kwargs):
for k, v in list(kwargs.items()):
setattr(self, k, v)
@property
def title(self):
"""A human-readable title for the playlist. Optional"""
return self._title
@title.setter
def title(self, title):
self._title = title
@property
def creator(self):
"""Human-readable name of the entity (author, authors, group, company, etc)
that authored the playlist. Optional"""
return self._creator
@creator.setter
def creator(self, creator):
self._creator = creator
@property
def annotation(self):
"""A human-readable comment on the playlist. This is character data,
not HTML, and it may not contain markup. Optional"""
return self._annotation
@annotation.setter
def annotation(self, annotation):
self._annotation = annotation
@property
def info(self):
"""URI of a web page to find out more about this playlist. Optional"""
return self._info
@info.setter
def info(self, info):
self._info = info
@property
def location(self):
"""Source URI for this playlist. Optional"""
return self._location
@location.setter
def location(self, location):
self._location = location
@property
def identifier(self):
"""Canonical ID for this playlist. Likely to be a hash or other
location-independent name. Optional"""
return self._identifier
@identifier.setter
def identifier(self, identifier):
self._identifier = identifier
@property
def image(self):
"""URI of an image to display in the absence of a trackList/image
element. Optional"""
return self._image
@image.setter
def image(self, image):
self._image = image
@property
def date(self):
"""Creation date (not last-modified date) of the playlist. Optional"""
return self._date
@date.setter
def date(self, date):
self._date = date
@property
def license(self):
"""URI of a resource that describes the license under which this
playlist was released. Optional"""
return self._license
@license.setter
def license(self, license):
self._license = license
@property
def meta(self):
return self._meta
def add_meta(self, key, value):
"""Add a meta element to the playlist."""
self._meta[key] = value
def del_meta(self, key):
"""Remove a meta element."""
del self._meta[key]
def add_link(self, key, value):
"""Add a link element to the playlist."""
self._link[key] = value
def del_link(self, key):
"""Remove a link element."""
del self._link[key]
def add_attribution(self, location, identifier):
self.attrbutions.append((location, identifier))
def truncate_attributions(self, numattributions):
self.attrbutions = self.attributions[-numattributions:]
# Todo: Attribution, Link, Meta, Extension
def add_extension(self, application):
pass
def make_extension_element(self, namespace, name, attributes, value):
pass
def remove_extension(self, application):
pass
@property
def track(self):
return self._trackList
@track.setter
def track(self, track):
self.add_track(track)
def add_track(self, track={}, **kwargs):
if isinstance(track, list):
for t in track:
self.add_track(t)
elif isinstance(track, Track):
self._trackList.append(track)
elif isinstance(track, dict) and len(track) > 0:
self._trackList.append(Track(track))
elif len(kwargs) > 0:
self._trackList.append(Track(kwargs))
def add_tracks(self, tracks):
for t in tracks:
self.add_track(t)
def toXml(self, encoding="utf-8", pretty_print=True):
root = ET.Element("{{{0}}}playlist".format(self.NS))
root.set("version", self.version)
self._addAttributesToXml(root, ["title", "info", "creator", "annotation",
"location", "identifier", "image", "date", "license"])
self._addDictionaryElements(root, "link", self._link)
self._addDictionaryElements(root, "meta", self._meta)
if len(self._trackList):
track_list = ET.SubElement(root, "{{{0}}}trackList".format(self.NS))
for track in self._trackList:
track_list = track.getXmlObject(track_list)
if pretty_print:
indent(root)
return ET.tostring(root, encoding)
class Track(XspfBase):
def __init__(self, obj={}, **kwargs):
self._location = ""
self._identifier = ""
self._title = ""
self._creator = ""
self._annotation = ""
self._info = ""
self._image = ""
self._album = ""
self._trackNum = ""
self._duration = ""
self._link = {}
self._meta = {}
if len(obj):
for k, v in list(obj.items()):
setattr(self, k, v)
if len(kwargs):
for k, v in list(kwargs.items()):
setattr(self, k, v)
@property
def location(self):
"""URI of resource to be rendered. Probably an audio resource, but MAY be any type of
resource with a well-known duration. Zero or more"""
return self._location
@location.setter
def location(self, location):
self._location = location
@property
def identifier(self):
"""ID for this resource. Likely to be a hash or other location-independent name,
such as a MusicBrainz identifier. MUST be a legal URI. Zero or more"""
return self._identifier
@identifier.setter
def identifier(self, identifier):
self._identifier = identifier
@property
def title(self):
"""Human-readable name of the track that authored the resource which defines the
duration of track rendering. Optional"""
return self._title
@title.setter
def title(self, title):
self._title = title
@property
def creator(self):
"""Human-readable name of the entity (author, authors, group, company, etc) that authored
the resource which defines the duration of track rendering."""
return self._creator
@creator.setter
def creator(self, creator):
self._creator = creator
@property
def annotation(self):
"""A human-readable comment on the track. This is character data, not HTML,
and it may not contain markup."""
return self._annotation
@annotation.setter
def annotation(self, annotation):
self._annotation = annotation
@property
def info(self):
"""URI of a place where this resource can be bought or more info can be found. Optional"""
return self._info
@info.setter
def info(self, info):
self._info = info
@property
def image(self):
"""URI of an image to display for the duration of the track. Optional"""
return self._image
@image.setter
def image(self, image):
self._image = image
@property
def album(self):
"""Human-readable name of the collection from which the resource which defines
the duration of track rendering comes. Optional"""
return self._album
@album.setter
def album(self, album):
self._album = album
@property
def trackNum(self):
"""Integer with value greater than zero giving the ordinal position of the media
on the album. Optional"""
return self._trackNum
@trackNum.setter
def trackNum(self, trackNum):
self._trackNum = trackNum
@property
def duration(self):
"""The time to render a resource, in milliseconds. Optional"""
return self._duration
@duration.setter
def duration(self, duration):
self._duration = duration
@property
def meta(self):
return self._meta
def add_meta(self, key, value):
"""Add a meta element to the playlist."""
self._meta[key] = value
def del_meta(self, key):
"""Remove a meta element."""
del self._meta[key]
def add_link(self, key, value):
"""Add a link element to the playlist."""
self._link[key] = value
def del_link(self, key):
"""Remove a link element."""
del self._link[key]
# Todo: Link, Meta, Extension
def getXmlObject(self, parent):
track = ET.SubElement(parent, "{{{0}}}track".format(self.NS))
self._addAttributesToXml(track, ["location", "identifier", "title", "creator",
"annotation", "info", "image", "album",
"trackNum", "duration"])
self._addDictionaryElements(track, "link", self._link)
self._addDictionaryElements(track, "meta", self._meta)
return parent
Spiff = Xspf