From 38d987b410d1ba26650e907807c6ee1cc9487e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20Mazi=C3=A8re?= Date: Wed, 28 Aug 2013 03:14:28 +0200 Subject: [PATCH] audioaddict backend --- modules/audioaddict/__init__.py | 24 ++++ modules/audioaddict/backend.py | 248 ++++++++++++++++++++++++++++++++ modules/audioaddict/favicon.png | Bin 0 -> 1419 bytes modules/audioaddict/test.py | 42 ++++++ 4 files changed, 314 insertions(+) create mode 100644 modules/audioaddict/__init__.py create mode 100644 modules/audioaddict/backend.py create mode 100644 modules/audioaddict/favicon.png create mode 100644 modules/audioaddict/test.py diff --git a/modules/audioaddict/__init__.py b/modules/audioaddict/__init__.py new file mode 100644 index 00000000..85ce2adf --- /dev/null +++ b/modules/audioaddict/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Pierre Mazière +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + + +from .backend import AudioAddictBackend + + +__all__ = ['AudioAddictBackend'] diff --git a/modules/audioaddict/backend.py b/modules/audioaddict/backend.py new file mode 100644 index 00000000..cc7eef26 --- /dev/null +++ b/modules/audioaddict/backend.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Pierre Mazière +# +# This file is part of weboob. +# +# weboob 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. +# +# weboob 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 weboob. If not, see . + + +from weboob.capabilities.radio import ICapRadio, Radio, Stream, Emission +from weboob.capabilities.collection import ICapCollection, Collection +from weboob.tools.backend import BaseBackend, BackendConfig +from weboob.tools.value import Value +from weboob.tools.browser import StandardBrowser +from weboob.tools.misc import to_unicode +import time + +__all__ = ['AudioAddictBackend'] + + +class AudioAddictBackend(BaseBackend, ICapRadio, ICapCollection): + NAME = 'audioaddict' + MAINTAINER = u'Pierre Mazière' + EMAIL = 'pierre.maziere@gmx.com' + VERSION = '0.h' + DESCRIPTION = u'Internet radios powered by audioaddict.com services' + LICENSE = 'AGPLv3+' + BROWSER = StandardBrowser + +# http://tobiass.eu/api-doc.html + NETWORKS = { + 'DI': { + 'desc': 'Digitally Imported addictive electronic music', + 'domain': 'di.fm', + 'streams': {'android_low':{'rate': 40, 'fmt': 'aac'}, + 'android': {'rate': 64, 'fmt': 'aac'}, + 'android_high': {'rate': 96, 'fmt': 'aac'}, + 'android_premium_low': {'rate': 40, 'fmt': 'aac'}, + 'android_premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'android_premium': {'rate': 128, 'fmt': 'aac'}, + 'android_premium_high': {'rate': 256, 'fmt': 'aac'}, + 'public1': {'rate': 64, 'fmt': 'aac'}, + 'public2': {'rate': 40, 'fmt': 'aac'}, + 'public3': {'rate': 96, 'fmt': 'mp3'}, + 'premium_low': {'rate': 40, 'fmt': 'aac'}, + 'premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'premium': {'rate': 128, 'fmt': 'aac'}, + 'premium_high': {'rate': 256, 'fmt': 'mp3'} + } + }, + 'SKYfm': { + 'desc': 'SKY FM radio', + 'domain': 'sky.fm', + 'streams': {'appleapp_low': {'rate': 40, 'fmt': 'aac'}, + 'appleapp': {'rate': 64, 'fmt': 'aac'}, + 'appleapp_high': {'rate': 96, 'fmt': 'mp3'}, + 'appleapp_premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'appleapp_premium': {'rate': 128, 'fmt': 'aac'}, + 'appleapp_premium_high': {'rate': 256, 'fmt': 'mp3'}, + 'public1': {'rate': 40, 'fmt': 'aac'}, + 'public5': {'rate': 40, 'fmt': 'wma'}, + 'public3': {'rate': 96, 'fmt': 'mp3'}, + 'premium_low': {'rate': 40, 'fmt': 'aac'}, + 'premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'premium': {'rate': 128, 'fmt': 'aac'}, + 'premium_high': {'rate': 256, 'fmt': 'mp3'} + } + }, + 'JazzRadio': { + 'desc': 'Jazz Radio', + 'domain': 'jazzradio.com', + 'streams': {'appleapp_low': {'rate': 40, 'fmt': 'aac'}, + 'appleapp': {'rate': 64, 'fmt': 'aac'}, + 'appleapp_premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'appleapp_premium': {'rate': 128, 'fmt': 'aac'}, + 'appleapp_premium_high': {'rate': 256, 'fmt': 'mp3'}, + 'public1': {'rate': 40, 'fmt': 'aac'}, + 'public3': {'rate': 64, 'fmt': 'mp3'}, + 'premium_low': {'rate': 40, 'fmt': 'aac'}, + 'premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'premium': {'rate': 128, 'fmt': 'aac'}, + 'premium_high': {'rate': 256, 'fmt': 'mp3'} + } + }, + 'RockRadio': { + 'desc': 'Rock Radio', + 'domain': 'rockradio.com', + 'streams': {'android_low': {'rate': 40, 'fmt': 'aac'}, + 'android': {'rate': 64, 'fmt': 'aac'}, + 'android_premium_medium': {'rate': 64, 'fmt': 'aac'}, + 'android_premium': {'rate': 128, 'fmt': 'aac'}, + 'android_premium_high': {'rate': 256, 'fmt': 'mp3'}, + 'public3': {'rate': 96, 'fmt': 'mp3'} + } + }, + } + + CONFIG = BackendConfig(Value('networks', + label='Selected Networks [%s](space separated)'%\ + ' '.join(NETWORKS.keys()), default=''), + Value('quality', label='Radio streaming quality', + choices={'h':'high','l':'low'}, + default='h') + ) + + HISTORY = {} + + def __init__(self, *a, **kw): + super(AudioAddictBackend, self).__init__(*a, **kw) + self.RADIOS = {} + + def _get_tracks_history(self, network): + self._fetch_radio_list() + domain=self.NETWORKS[network]['domain'] + url='http://api.audioaddict.com/v1/%s/track_history' %\ + (domain[:domain.rfind('.')]) + self.HISTORY[network] = self.browser.location(url) + return self.HISTORY + + def create_default_browser(self): + return self.create_browser(parser='json') + + def _get_stream_name(self,network,quality): + streamName='public3' + for name in self.NETWORKS[network]['streams'].keys(): + if name.startswith('public') and \ + self.NETWORKS[network]['streams'][name]['rate']>=64: + if quality=='h': + streamName=name + break + else: + if quality=='l': + streamName=name + break + return streamName + + def _fetch_radio_list(self): + quality=self.config['quality'].get() + for network in self.config['networks'].get().split(): + streamName=self._get_stream_name(network,quality) + if not self.RADIOS: + self.RADIOS={} + if not network in self.RADIOS: + document = self.browser.location('http://listen.%s/%s'%\ + (self.NETWORKS[network]['domain'], + streamName)) + self.RADIOS[network]={} + for info in document: + radio = info['key'] + self.RADIOS[network][radio] = {} + self.RADIOS[network][radio]['id'] = info['id'] + self.RADIOS[network][radio]['description'] = info['description'] + self.RADIOS[network][radio]['name'] = info['name'] + self.RADIOS[network][radio]['playlist'] = info['playlist'] + + return self.RADIOS + + def iter_radios_search(self, pattern): + self._fetch_radio_list() + + pattern = pattern.lower() + for network in self.config['networks'].get().split(): + for radio in self.RADIOS[network]: + radio_dict = self.RADIOS[network][radio] + if pattern in radio_dict['name'].lower() or pattern in radio_dict['description'].lower(): + yield self.get_radio(network,radio) + + def iter_resources(self, objs, split_path): + self._fetch_radio_list() + + if Radio in objs: + for network in self.config['networks'].get().split(): + if split_path == [network]: + for radio in self.RADIOS[network]: + yield self.get_radio(network,radio) + return + for network in self.config['networks'].get().split(): + yield Collection([network],self.NETWORKS[network]['desc']) + + def get_current(self, network, radio): + channel={} + if not network in self.HISTORY: + self._get_tracks_history(network) + channel=self.HISTORY[network].get(str(self.RADIOS[network][radio]['id'])) + else: + now=time.time() + channel=self.HISTORY[network].get(str(self.RADIOS[network][radio]['id'])) + if channel is None: + return 'Unknown', 'Unknown' + if (channel.get('started')+channel.get('duration')) < now: + self._get_tracks_history(network) + channel=self.HISTORY[network].get(str(self.RADIOS[network][radio]['id'])) + + artist = u'' + (channel.get('artist', '') or 'Unknown') + title = u''+(channel.get('title', '') or 'Unknown') + + return artist, title + + def get_radio(self, network, radio): + self._fetch_radio_list() + + if not isinstance(radio, Radio): + radio = Radio(radio) + + if not radio.id in self.RADIOS[network]: + return None + + radio_dict = self.RADIOS[network][radio.id] + radio.title = radio_dict['name'] + radio.description = radio_dict['description'] + radio._network=network + + artist, title = self.get_current(network,radio.id) + current = Emission(0) + current.artist = artist + current.title = title + radio.current = current + + radio.streams = [] + name=self._get_stream_name(network,self.config['quality'].get()) + stream = Stream(name) + stream.title = u'%s %skbps' %\ + (self.NETWORKS[network]['streams'][name]['fmt'], + self.NETWORKS[network]['streams'][name]['rate']) + stream.url = 'http://listen.%s/%s/%s.pls'%\ + (self.NETWORKS[network]['domain'],name,radio.id) + radio.streams.append(stream) + return radio + + def fill_radio(self, radio, fields): + if 'current' in fields: + radio.current = Emission(0) + radio.current.artist, radio.current.title = self.get_current(radio._network,radio.id) + return radio + + OBJECTS = {Radio: fill_radio} + diff --git a/modules/audioaddict/favicon.png b/modules/audioaddict/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..db0fcc6df9001704ce8f669fac5577e0481d212d GIT binary patch literal 1419 zcmV;61$6p}P)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{00i?%L_t(|+U;AR)DMkSpm#V_VxfGqmS(G#}06B zPfX#kzi+>%Bb%L|^!tG4fjS*C|9l>3w?U2*Dfxb1;CVo&Lq`7jyrA6%9EYaV0lsgX zJb)Kp-;ey?_kHu^ffrv}IJ*MWYW7(~8IvNMT>%ab>?4XYCPg@74UldZSXcn{dUm4e zy1>H&Xl~AGT(>J&SO9uGvKr^Qf`phC>UHaJ^}2oXz{?mLxLg`s zjxshaxLgwC64!;OmbwUJRt<4of#XOraAIJ5e=kfV0!;jHr6OD|hks8b+-ixb-QTki zaoBWHsj!sG;olRnv|4P0l{4)rQfXzS0_^Oll-8;*Yc)WGqQXIhjNx*LkZNZ~9g!t5 zQ-w34K&cek-U0|;UkhJcsC`N$V+xc?teQ%$uURfG)IOz>83nWj;LA&OV#~`T-$Fs{ zdv&D^=<-sX*z)oyu|h%Zdv(PLXn;;q$9u6-iM>rr8O}fhbYvQjLKl$%F;j+9YXCC~ zd!D+j8;y~Ft)})Z6oi?Tc%G+j>qcWVzE)HF77DDy(`o>po~jd?nGp_xk^j?^u;Zx5 zoSlW@ot~-_nwenAiaF*HC&qH-BDJ3CtM=jVZsA0rdf01mUWqy54B zJjH=C&;T1kPEsk$Ia7vH7eJn$fgeATkEqdrOn9n%ewJy$q1TfL9dL963Ig*r$xxap z2!NxbNpJ`PN*WG9K$GAM3ZNm6EG~jpSKlVMwg&M$YhvoSy0|DH^5)Bk=QQ^oVrCJUM}koi0B<%CzCo>4-%K93FxO1AG1(Mnr=FaCkTl zhrxiR1&6_a#^DSKpyR*e0G~gHJ8W;OPl$fQ(DC1KLcWkf~j`1QR z%E@QSa0VLi&I<6B0>xr_6et#@SWJ%s#UiUUfX~lUOI`pu#~*3J2_VvJvO)vW>_-3x z_zP*JKc>)DY zZhw6JYG3kdb>g3WY@>sGT5w~}9fXsUNfai^!z?6NC$=!LU_#ch&7rB;0P> zQ{d)i;(WXKTiL2aEbX>E1#WKi%j&mp{CXMWZdcfK)dkqxl&HolE3fA2VoqE. + + +from weboob.tools.test import BackendTest +from weboob.capabilities.radio import Radio + + +class AudioAddictTest(BackendTest): + BACKEND = 'audioaddict' + + def test_audioaddict(self): + ls = list(self.backend.iter_resources((Radio, ), [])) + self.assertTrue(len(ls) > 0) + + search = list(self.backend.iter_radios_search('classic')) + self.assertTrue(len(search) > 0) + + radio = self.backend.get_radio('RockRadio','classicrock') + self.assertTrue(radio.title) + self.assertTrue(radio.description) + self.assertTrue(radio.current.title) + self.assertTrue(radio.current.artist) + self.assertTrue(radio.streams[0].url) + self.assertTrue(radio.streams[0].title) +