Manage Albums and Playlists in radioob

This commit is contained in:
Bezleputh 2014-02-18 23:04:26 +01:00 committed by Florent
commit e33c177212
2 changed files with 301 additions and 54 deletions

View file

@ -24,7 +24,7 @@ import re
import requests
from weboob.capabilities.radio import ICapRadio, Radio
from weboob.capabilities.audio import ICapAudio, BaseAudio
from weboob.capabilities.audio import ICapAudio, BaseAudio, Playlist, Album
from weboob.capabilities.base import empty
from weboob.tools.application.repl import ReplApplication, defaultcount
from weboob.tools.application.media_player import InvalidMediaPlayer, MediaPlayer, MediaPlayerNotFound
@ -49,6 +49,75 @@ class RadioListFormatter(PrettyFormatter):
return result
class SongListFormatter(PrettyFormatter):
MANDATORY_FIELDS = ('id', 'title')
def get_title(self, obj):
result = obj.title
if hasattr(obj, 'author') and not empty(obj.author):
result += ' (%s)' % obj.author
return result
def get_description(self, obj):
result = ''
if hasattr(obj, 'description') and not empty(obj.description):
result += '%-30s' % obj.description
return result
class AlbumTrackListInfoFormatter(PrettyFormatter):
MANDATORY_FIELDS = ('id', 'title', 'tracks_list')
def get_title(self, obj):
result = obj.title
if hasattr(obj, 'author') and not empty(obj.author):
result += ' (%s)' % obj.author
return result
def get_description(self, obj):
result = ''
for song in obj.tracks_list:
result += '- %s%-30s%s ' % (self.BOLD, song.title, self.NC)
if hasattr(song, 'duration') and not empty(song.duration):
result += '%-10s ' % song.duration
else:
result += '%-10s ' % ' '
result += '(%s)\r\n\t' % (song.id)
return result
class PlaylistTrackListInfoFormatter(PrettyFormatter):
MANDATORY_FIELDS = ('id', 'title', 'tracks_list')
def get_title(self, obj):
return obj.title
def get_description(self, obj):
result = ''
for song in obj.tracks_list:
result += '- %s%-30s%s ' % (self.BOLD, song.title, self.NC)
if hasattr(song, 'author') and not empty(song.author):
result += '(%-15s) ' % song.author
if hasattr(song, 'duration') and not empty(song.duration):
result += '%-10s ' % song.duration
else:
result += '%-10s ' % ' '
result += '(%s)\r\n\t' % (song.id)
return result
class Radioob(ReplApplication):
APPNAME = 'radioob'
VERSION = '0.j'
@ -57,11 +126,16 @@ class Radioob(ReplApplication):
"like the current song."
SHORT_DESCRIPTION = "search, show or listen to radio stations"
CAPS = (ICapRadio, ICapAudio)
EXTRA_FORMATTERS = {'radio_list': RadioListFormatter}
COMMANDS_FORMATTERS = {'ls': 'radio_list',
'search': 'radio_list',
EXTRA_FORMATTERS = {'radio_list': RadioListFormatter,
'song_list': SongListFormatter,
'album_tracks_list_info': AlbumTrackListInfoFormatter,
'playlist_tracks_list_info': PlaylistTrackListInfoFormatter,
}
COMMANDS_FORMATTERS = {'ls': 'radio_list',
'playlist': 'radio_list',
}
}
COLLECTION_OBJECTS = (Radio, BaseAudio, )
PLAYLIST = []
@ -152,24 +226,30 @@ class Radioob(ReplApplication):
try:
stream_id = int(stream_id)
except (ValueError,TypeError):
except (ValueError, TypeError):
stream_id = 0
radio = self.get_object(_id, 'get_radio')
audio = self.get_object(_id, 'get_audio')
obj = self.retrieve_obj(_id)
if radio is None and audio is None:
print >>sys.stderr, 'Radio or Audio file not found:', _id
if obj is None:
print >>sys.stderr, 'No object matches with this id:', _id
return 3
if audio is None:
if isinstance(obj, Radio):
try:
stream = radio.streams[stream_id]
streams = [obj.streams[stream_id]]
except IndexError:
print >>sys.stderr, 'Stream #%d not found' % stream_id
print >>sys.stderr, 'Stream %d not found' % stream_id
return 1
elif isinstance(obj, BaseAudio):
streams = [obj]
else:
stream = audio
streams = obj.tracks_list
if len(streams) == 0:
print >>sys.stderr, 'Radio or Audio file not found:', _id
return 3
try:
player_name = self.config.get('media_player')
@ -178,31 +258,58 @@ class Radioob(ReplApplication):
self.logger.debug(u'You can set the media_player key to the player you prefer in the radioob '
'configuration file.')
r = requests.get(stream.url, stream=True)
buf = r.iter_content(512).next()
r.close()
playlistFormat = None
for line in buf.split("\n"):
if playlistFormat is None:
if line == "[playlist]":
playlistFormat = "pls"
elif line == "#EXTM3U":
playlistFormat = "m3u"
else:
break
elif playlistFormat == "pls":
if line.startswith('File'):
stream.url = line.split('=', 1).pop(1).strip()
break
elif playlistFormat == "m3u":
if line[0] != "#":
stream.url = line.strip()
break
for stream in streams:
if isinstance(stream, BaseAudio) and not stream.url:
stream = self.get_object(stream.id, 'get_audio')
else:
r = requests.get(stream.url, stream=True)
buf = r.iter_content(512).next()
r.close()
playlistFormat = None
for line in buf.split("\n"):
if playlistFormat is None:
if line == "[playlist]":
playlistFormat = "pls"
elif line == "#EXTM3U":
playlistFormat = "m3u"
else:
break
elif playlistFormat == "pls":
if line.startswith('File'):
stream.url = line.split('=', 1).pop(1).strip()
break
elif playlistFormat == "m3u":
if line[0] != "#":
stream.url = line.strip()
break
self.player.play(stream, player_name=player_name, player_args=media_player_args)
self.player.play(stream, player_name=player_name, player_args=media_player_args)
except (InvalidMediaPlayer, MediaPlayerNotFound) as e:
print '%s\nRadio URL: %s' % (e, stream.url)
def retrieve_obj(self, _id):
if self.interactive:
try:
obj = self.objects[int(_id) - 1]
_id = obj.id
except (IndexError, ValueError):
pass
m = re.match('^(\w+)\.(.*)', _id)
if m:
if m.group(1) == 'album':
return self.get_object(_id, 'get_album')
elif m.group(1) == 'playlist':
return self.get_object(_id, 'get_playlist')
else:
return self.get_object(_id, 'get_audio')
return self.get_object(_id, 'get_radio')
def do_playlist(self, line):
"""
playlist cmd [args]
@ -269,7 +376,6 @@ class Radioob(ReplApplication):
print >>sys.stderr, 'Playlist command only support "add", "remove", "display" and "export" arguments.'
return 2
def complete_info(self, text, line, *ignored):
args = line.split(' ')
if len(args) == 2:
@ -285,35 +391,67 @@ class Radioob(ReplApplication):
print >>sys.stderr, 'This command takes an argument: %s' % self.get_command_help('info', short=True)
return 2
radio = self.get_object(_id, 'get_radio')
audio = self.get_object(_id, 'get_audio')
if radio is None and audio is None:
print >>sys.stderr, 'Radio or Audio file not found:', _id
obj = self.retrieve_obj(_id)
if isinstance(obj, Album):
self.set_formatter('album_tracks_list_info')
elif isinstance(obj, Playlist):
self.set_formatter('playlist_tracks_list_info')
if obj is None:
print >>sys.stderr, 'No object matches with this id:', _id
return 3
if audio is None:
self.format(radio)
else:
self.format(audio)
self.format(obj)
@defaultcount(10)
def do_search(self, pattern=None):
"""
search PATTERN
search (radio|song|album|playlist) PATTERN
List radios matching a PATTERN.
List (radio|song|album|playlist) matching a PATTERN.
If PATTERN is not given, this command will list all the radios.
If PATTERN is not given, this command will list all the (radio|song|album|playlist).
"""
if not pattern:
print >>sys.stderr, 'This command takes an argument: %s' % self.get_command_help('playlist')
return 2
cmd, args = self.parse_command_args(pattern, 2, req_n=1)
if not args:
args = ""
self.set_formatter_header(u'Search pattern: %s' % pattern if pattern else u'All radios')
self.change_path([u'search'])
for backend, radio in self.do('iter_radios_search', pattern=pattern):
self.add_object(radio)
self.format(radio)
for backend, audio in self.do('search_audio', pattern=pattern):
self.add_object(audio)
self.format(audio)
if cmd == "radio":
self.set_formatter('radio_list')
for backend, radio in self.do('iter_radios_search', pattern=args):
self.add_object(radio)
self.format(radio)
elif cmd == "song":
self.set_formatter('song_list')
for backend, audio in self.do('search_audio', pattern=args):
self.add_object(audio)
self.format(audio)
elif cmd == "album":
self.set_formatter('song_list')
for backend, album in self.do('search_album', pattern=args):
self.add_object(album)
self.format(album)
elif cmd == "playlist":
self.set_formatter('song_list')
for backend, playlist in self.do('search_playlist', pattern=args):
self.add_object(playlist)
self.format(playlist)
else:
print >>sys.stderr, 'Search command only supports "radio", "song", "album" and "playlist" arguments.'
return 2
def do_ls(self, line):
"""

View file

@ -17,17 +17,74 @@
# You should have received a copy of the GNU Affero General Public License
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
import re
from datetime import timedelta
from .image import BaseImage
from .base import Field, StringField
from .base import Field, StringField, IntField, CapBaseObject
from .file import ICapFile, BaseFile
__all__ = ['BaseAudio', 'ICapAudio']
def decode_id(decode_id):
def wrapper(func):
def inner(self, *args, **kwargs):
arg = unicode(args[0])
_id = decode_id(arg)
if _id is None:
return None
new_args = [_id]
new_args.extend(args[1:])
return func(self, *new_args, **kwargs)
return inner
return wrapper
class Album(CapBaseObject):
"""
Represent an album
"""
title = StringField('album name')
author = StringField('artist name')
year = IntField('release year')
thumbnail = Field('Image associated to the album', BaseImage)
tracks_list = Field('list of tracks', list)
def __init__(self, _id):
CapBaseObject.__init__(self, unicode("album.%s" % _id))
@classmethod
def decode_id(cls, _id):
if _id:
m = re.match('^(album)\.(.*)', _id)
if m:
return m.group(2)
return _id
class Playlist(CapBaseObject):
"""
Represent a playlist
"""
title = StringField('playlist name')
tracks_list = Field('list of tracks', list)
def __init__(self, _id):
CapBaseObject.__init__(self, unicode("playlist.%s" % _id))
@classmethod
def decode_id(cls, _id):
if _id:
m = re.match('^(playlist)\.(.*)', _id)
if m:
return m.group(2)
return _id
class BaseAudio(BaseFile):
"""
Represent an audio file
@ -37,6 +94,17 @@ class BaseAudio(BaseFile):
format = StringField('file format')
thumbnail = Field('Image associated to the file', BaseImage)
def __init__(self, _id):
BaseFile.__init__(self, unicode("audio.%s" % _id))
@classmethod
def decode_id(cls, _id):
if _id:
m = re.match('^(audio)\.(.*)', _id)
if m:
return m.group(2)
return _id
class ICapAudio(ICapFile):
"""
@ -53,6 +121,25 @@ class ICapAudio(ICapFile):
"""
return self.search_file(pattern, sortby)
def search_album(self, pattern, sortby=ICapFile.SEARCH_RELEVANCE):
"""
search for an album
:param pattern: pattern to search on
:type pattern: str
:rtype: iter[:class:`Album`]
"""
raise NotImplementedError()
def search_playlist(self, pattern, sortby=ICapFile.SEARCH_RELEVANCE):
"""
search for an album
:param pattern: pattern to search on
:type pattern: str
:rtype: iter[:class:`Playlist`]
"""
raise NotImplementedError()
@decode_id(BaseAudio.decode_id)
def get_audio(self, _id):
"""
Get an audio file from an ID.
@ -62,3 +149,25 @@ class ICapAudio(ICapFile):
:rtype: :class:`BaseAudio`]
"""
return self.get_file(_id)
@decode_id(Playlist.decode_id)
def get_playlist(self, _id):
"""
Get a playlist from an ID.
:param id: playlist ID
:type id: str
:rtype: :class:`Playlist`]
"""
raise NotImplementedError()
@decode_id(Album.decode_id)
def get_album(self, _id):
"""
Get an album from an ID.
:param id: album ID
:type id: str
:rtype: :class:`Album`]
"""
raise NotImplementedError()