Last active
May 18, 2022 09:12
-
-
Save neko-neko-nyan/091a3903f967f9e6ae27e844f707eac8 to your computer and use it in GitHub Desktop.
Writing XSPF playlists with VLC extensions in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import typing | |
class Node: | |
def write(self, f: 'XMLWriter'): | |
raise NotImplementedError() | |
class WithTags: | |
def __init__(self, **kwargs): | |
self.title = kwargs.get('title') | |
self.creator = kwargs.get('creator') | |
self.annotation = kwargs.get('annotation') | |
self.image = kwargs.get('image') | |
self.options = [(name, value) for name, value in kwargs.items() if name[0] == ':'] | |
def _write_tags(self, f): | |
f.simple_tags({ | |
'title': self.title, | |
'creator': self.creator, | |
'annotation': self.annotation, | |
'image': self.image, | |
}) | |
def _write_options(self, f): | |
for name, value in self.options: | |
f.simple('vlc:option', f'{name}={value}') | |
def option(self, name, value): | |
self.options.append((name, value)) | |
return self | |
class Container: | |
def __init__(self): | |
self.children = [] # type: typing.List[Node] | |
def add(self, item: Node): | |
self.children.append(item) | |
return item | |
@property | |
def all_tracks(self) -> 'typing.List[Track]': | |
res = [] | |
for i in self.children: | |
if isinstance(i, Container): | |
res += i.all_tracks | |
elif isinstance(i, Track): | |
res.append(i) | |
else: | |
raise TypeError('Only Containers and Tracks may be placed into Container') | |
return res | |
def _write_children(self, f): | |
for i in self.children: | |
i.write(f) | |
def add_folder(self, title): | |
return self.add(Folder(title)) | |
def add_track(self, **kwargs): | |
return self.add(Track(**kwargs)) | |
class Track(Node, WithTags): | |
def __init__(self, **kwargs): | |
super().__init__(**kwargs) | |
self.location = kwargs.get('location') | |
self.info = kwargs.get('info') | |
self.album = kwargs.get('album') | |
self.trackNum = kwargs.get('trackNum') | |
self.duration = kwargs.get('duration') | |
self._tid = None | |
def write_header(self, f, tid): | |
self._tid = tid | |
f.begin('track') | |
self._write_tags(f) | |
f.simple_tags({ | |
'location': self.location, | |
'info': self.info, | |
'album': self.album, | |
'trackNum': self.trackNum, | |
'duration': self.duration, | |
}) | |
f.begin('extension', application="http://www.videolan.org/vlc/playlist/0") | |
self._write_options(f) | |
f.simple('vlc:id', tid) | |
f.end() | |
f.end() | |
def write(self, f): | |
f.simple('vlc:item', tid=self._tid) | |
class Folder(Node, Container): | |
def __init__(self, title): | |
super().__init__() | |
self.title = title | |
def write(self, f): | |
f.begin('vlc:node', title=self.title) | |
self._write_children(f) | |
f.end() | |
class Playlist(Node, WithTags, Container): | |
def __init__(self, **kwargs): | |
WithTags.__init__(self, **kwargs) | |
Container.__init__(self) | |
def write(self, f): | |
f.begin('playlist', version="1", xmlns="http://xspf.org/ns/0/", | |
**{'xmlns:vlc': "http://www.videolan.org/vlc/playlist/ns/0/"}) | |
self._write_tags(f) | |
f.begin('trackList') | |
for tid, i in enumerate(self.all_tracks): | |
i.write_header(f, tid) | |
f.end() | |
f.begin('extension', application="http://www.videolan.org/vlc/playlist/0") | |
self._write_options(f) | |
self._write_children(f) | |
f.end() | |
f.end() | |
class XMLWriter: | |
ESCAPING_MAP = { | |
"<": "<", | |
">": ">", | |
"\"": """, | |
"'": "'", | |
"&": "&", | |
} | |
def __init__(self, f): | |
self._stream = f | |
self._path = [] | |
self._stream.write('<?xml version="1.0" encoding="UTF-8"?>') | |
def begin(self, name: str, **kwargs): | |
self._path.append(name) | |
self._stream.write(f'<{name} {" ".join(self._make_attrs(kwargs))}>') | |
def end(self): | |
name = self._path.pop() | |
self._stream.write(f'</{name}>') | |
def simple(self, name, value=None, **kwargs): | |
attrs = " ".join(self._make_attrs(kwargs)) | |
if value is None: | |
self._stream.write(f'<{name} {attrs} />') | |
else: | |
self._stream.write(f'<{name} {attrs}>{self._escape(value)}</{name}>') | |
def simple_tags(self, dct: dict): | |
for name, value in dct.items(): | |
if value is not None: | |
self.simple(name, value) | |
def _make_attrs(self, dct): | |
for name, value in dct.items(): | |
if value is not None: | |
yield f'{name}="{self._escape(value)}"' | |
def _escape(self, data): | |
data = str(data) | |
for before, after in self.ESCAPING_MAP.items(): | |
data = data.replace(before, after) | |
return data |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment