I've created a file system, shoutcastfs, which enables you to mount the Shoutcast Radio directory as a file system. The genres are represented as directories and stations as files. Each file contains the station's playlist and the files are suffixed with .pls which makes it possible to load the playlist in a media player such as Amarok by double-clicking the file.
Of course, I'm using pyshoutcast (Python shoutcast API) to access the shoutcast service.
#!/usr/bin/env python # -*- coding: utf-8 -*- import errno import fuse import stat import os import shoutcast fuse.fuse_python_api = (0, 2) _shoutcastApi = shoutcast.ShoutCast() class RootInfo(fuse.Stat): def __init__(self): fuse.Stat.__init__(self) self.st_mode = stat.S_IFDIR | 0755 self.st_nlink = 2 self._genres = {} @property def genres(self): if not self._genres: for g in _shoutcastApi.genres(): self._genres[g] = GenreInfo(g) return self._genres class GenreInfo(fuse.Stat): def __init__(self, name): fuse.Stat.__init__(self) self.st_mode = stat.S_IFDIR | 0755 self.st_nlink = 2 self.name = name self._stations = {} @property def stations(self): if not self._stations: for s in _shoutcastApi.stations(self.name): name = '{0}.pls'.format(s[0]) name = name.replace('/', '|') self._stations[name] = StationInfo(name, s[1]) return self._stations class StationInfo(fuse.Stat): def __init__(self, name, station_id): fuse.Stat.__init__(self) self.st_mode = stat.S_IFREG | 0644 self.st_nlink = 1 # Hope no playlist exceeds this size self.st_size = 4096 self.name = name self.station_id = station_id self._content = None @property def content(self): if self._content is None: self._content = _shoutcastApi.tune_in(self.station_id).read() return self._content class ShoutcastFS(fuse.Fuse): def __init__(self, *args, **kw): fuse.Fuse.__init__(self, *args, **kw) self.root = RootInfo() def split_path(self, path): """ Returns genre and station """ if path == '/': return (None, None) parts = path.split('/')[1:] if len(parts) == 1: return (parts[0], None) else: return parts def getattr(self, path): genre, station = self.split_path(path) if genre is None: stat = self.root else: stat = self.root.genres.get(genre) if not stat: return -errno.ENOENT if station: stat = stat.stations.get(station) if not stat: return -errno.ENOENT return stat def readdir(self, path, offset): yield fuse.Direntry('.') yield fuse.Direntry('..') if path == '/': entries = self.root.genres.keys() else: entries = self.root.genres[path[1:]].stations.keys() for e in entries: yield fuse.Direntry(e) def open(self, path, flags): # Only support for 'READ ONLY' flag access_flags = os.O_RDONLY | os.O_WRONLY | os.O_RDWR if flags & access_flags != os.O_RDONLY: return -errno.EACCES else: return 0 def read(self, path, size, offset): genre, station = self.split_path(path) info = self.root.genres[genre].stations[station] if offset < info.st_size: if offset + size > info.st_size: size = info.st_size - offset return info.content[offset:offset+size] else: return '' if __name__ == '__main__': fs = ShoutcastFS() fs.parse(errex=1) fs.multithreaded = False fs.main()
To try the file system, run:
$ # Download shoutcast.py $ wget http://github.com/mariob/pyshoutcast/raw/master/src/shoutcast.py $ mkdir mnt $ ./shoutcastfs mnt $ cd mnt/ $ ls ...A list of genres... $ cd Samba $ ls ...A list of 'Samba' stations... $ cat [station name] ...Playlist data...Have fun!
No comments:
Post a Comment