zeya team mailing list archive
-
zeya team
-
Mailing list archive
-
Message #00043
FEATURE/PATCH: Support m3u playlists as backend
Hi Phil, all:
Pls. find attched the m3u.py file implementing the m3u backend and
patches to existing files.
I have defined the following format for using m3u playlists:
./zeya.py --backend=playlist --path=/home/r00t/Music/Songbird/Highest\
Rated.m3u --port=9090
Find a sample m3u list attached.
Going ahead, I will probably add support for PLS files using the same
backend and choose the backend class depending upon the playlist file
extension.
Pls. try it out and let me know how it looks!
Best,
Amit
--
Journal: http://amitksaha.wordpress.com
µ-blog: http://twitter.com/amitsaha
IRC: cornucopic on #scheme, #lisp, #math, #linux
# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Amit Saha
#
# This file is part of Zeya.
#
# Zeya is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# Zeya is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Zeya. If not, see <http://www.gnu.org/licenses/>.
# m3u backend
import os
import os.path
import sys
import tagpy
from backends import LibraryBackend
from common import tokenize_filename
TITLE='title'
ARTIST='artist'
ALBUM='album'
class M3uBackend(LibraryBackend):
def __init__(self,file_path=None):
self.m3u_file = file_path
# check if the file exists at all?
if not os.path.exists(self.m3u_file):
print "No m3u file found at %r", self.m3u_file
print "Please check the m3u file location, or try the --directory switch instead"
sys.exit(1)
try:
self._infile = open(self.m3u_file)
except IOError:
print "Could not read the playlist (%r)", self.m3u_file
sys.exit(1)
def get_library_contents(self):
#sequence of dicts contanining the metadata of all the songs
self.library=[]
#sequence of dicts of filenames
self.file_list={}
next_index = 0
for filename in open(self.m3u_file):
#ignore lines in the m3u file beginning with #
if filename[0] != '#':
try:
metadata = extract_metadata(os.path.abspath(filename.rstrip('\r\n'))) #strip the \r\n at the end of each line
metadata['key'] = next_index
self.file_list[next_index]= filename
self.library.append(metadata)
next_index = next_index + 1
except ValueError:
continue
return self.library
def get_filename_from_key(self, key):
return self.file_list[int(key)].rstrip('\r\n')
def extract_metadata(filename, tagpy_module=tagpy):
# same as in directory.py
"""
Returns a metadata dictionary (a dictionary {ARTIST: ..., ...}) containing
metadata (artist, title, and album) for the song in question.
filename: a string supplying a filename.
tagpy_module: a reference to the tagpy module. This can be faked out for
unit testing.
"""
# tagpy can do one of three things:
#
# * Return legitimate data. We'll load that data.
# * Return None. We'll assume this is a music file but that it doesn't have
# metadata. Create an entry for it.
# * Throw ValueError. We'll assume this is not something we could play.
# Don't create an enty for it.
try:
tag = tagpy_module.FileRef(filename).tag()
except:
raise ValueError("Error reading metadata from %r" % (filename,))
# If no metadata is available, set the title to be the basename of the
# file. (We have to ensure that the title, in particular, is not empty
# since the user has to click on it in the web UI.)
metadata = {
TITLE: os.path.basename(filename).decode("UTF-8"),
ARTIST: '',
ALBUM: album_name_from_path(tag, filename),
}
if tag is not None:
metadata[ARTIST] = tag.artist
# Again, do not allow metadata[TITLE] to be an empty string, even if
# tag.title is an empty string.
metadata[TITLE] = tag.title or metadata[TITLE]
metadata[ALBUM] = tag.album or metadata[ALBUM]
return metadata
def album_name_from_path(tag, filename):
"""
Returns an appropriate Unicode string to use for the album name if the tag
is empty.
"""
if tag is not None and (tag.artist or tag.album):
return u''
# Use the trailing components of the path.
path_components = [x for x in os.path.dirname(filename).split(os.sep) if x]
if len(path_components) >= 2:
return os.sep.join(path_components[-2:]).decode("UTF-8")
elif len(path_components) == 1:
return path_components[0].decode("UTF-8")
return u''
diff --git a/decoders.py b/decoders.py
index 91b015f..1e80a66 100644
--- a/decoders.py
+++ b/decoders.py
@@ -56,6 +56,7 @@ def has_decoder(filename):
If it isn't, print a warning, but only if no previous warning has been
issued for the same decoder.
"""
+
if not is_decoder_registered(filename):
return False
extension = get_extension(filename)
diff --git a/directory.py b/directory.py
index 649acca..c16f889 100644
--- a/directory.py
+++ b/directory.py
@@ -186,6 +186,7 @@ def extract_metadata(filename, tagpy_module = tagpy):
# metadata. Create an entry for it.
# * Throw ValueError. We'll assume this is not something we could play.
# Don't create an enty for it.
+
try:
tag = tagpy_module.FileRef(filename).tag()
except:
diff --git a/options.py b/options.py
index 1bd170b..aba8b6a 100644
--- a/options.py
+++ b/options.py
@@ -28,7 +28,7 @@ DEFAULT_PORT = 8080
DEFAULT_BITRATE = 64 #kbits/s
DEFAULT_BACKEND = 'dir'
-valid_backends = ['rhythmbox', 'dir']
+valid_backends = ['rhythmbox', 'dir', 'playlist']
class BadArgsError(Exception):
"""
@@ -70,6 +70,7 @@ def get_options(remaining_args):
help_msg = True
if flag in ("--backend",):
backend_type = value
+ print "Backend", backend_type
if backend_type not in valid_backends:
raise BadArgsError("Unsupported backend type %r"
% (backend_type,))
@@ -92,11 +93,13 @@ def get_options(remaining_args):
port = int(value)
except ValueError:
raise BadArgsError("Invalid port setting %r" % (value,))
- if backend_type != 'dir' and path is not None:
+ if backend_type != 'dir' and backend_type != 'playlist' and path is not None:
print "Warning: --path was set but is ignored for --backend=%s" \
% (backend_type,)
if backend_type == 'dir' and path is None:
path = "."
+ if backend_type == 'playlist' and path is None:
+ raise BadArgsError("Specify --path for playlist backend")
return (help_msg, backend_type, bitrate, port, path, basic_auth_file)
def print_usage():
@@ -111,9 +114,13 @@ Options:
Specify the backend to use. Acceptable values:
dir: (default) read a directory's contents recursively; see --path
rhythmbox: read from current user's Rhythmbox library
+ playlist: read from the user specified playlist (m3u) file;
+ (the playlist location will be given with --path,
+ which if missing should result in an error)
--path=PATH
Directory in which to look for music, under --backend=dir. (Default: ./)
+ Absolute file path, under --backend=playlist
-b, --bitrate=N
Specify the bitrate for output streams, in kbits/sec. (default: 64)
diff --git a/zeya.py b/zeya.py
index 7ce4224..2a2ee62 100755
--- a/zeya.py
+++ b/zeya.py
@@ -308,6 +308,9 @@ def get_backend(backend_type):
elif backend_type == 'dir':
from directory import DirectoryBackend
return DirectoryBackend(path)
+ elif backend_type == 'playlist':
+ from m3u import M3uBackend
+ return M3uBackend(path)
else:
raise ValueError("Invalid backend %r" % (backend_type,))
@@ -319,6 +322,7 @@ def run_server(backend, port, bitrate, basic_auth_file=None):
library_contents = \
[ s for s in library_contents \
if decoders.has_decoder(backend.get_filename_from_key(s['key'])) ]
+
if not library_contents:
print "WARNING: no tracks were found. Check that you've specified " \
+ "the right backend/path."
Attachment:
Highest Rated.m3u
Description: audio/mpegurl
Follow ups