From 884cc27b8e1ac49db3f92a5d8b36ce80a9e9622d Mon Sep 17 00:00:00 2001 From: Roger Philibert Date: Tue, 13 Aug 2013 23:56:40 +0200 Subject: [PATCH] new radio backend: somafm Signed-off-by: Roger Philibert --- modules/somafm/__init__.py | 24 ++++++++ modules/somafm/backend.py | 120 +++++++++++++++++++++++++++++++++++++ modules/somafm/favicon.png | Bin 0 -> 3704 bytes modules/somafm/test.py | 43 +++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 modules/somafm/__init__.py create mode 100644 modules/somafm/backend.py create mode 100644 modules/somafm/favicon.png create mode 100644 modules/somafm/test.py diff --git a/modules/somafm/__init__.py b/modules/somafm/__init__.py new file mode 100644 index 00000000..45e74cda --- /dev/null +++ b/modules/somafm/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Roger Philibert +# +# 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 SomaFMBackend + + +__all__ = ['DIFMBackend'] diff --git a/modules/somafm/backend.py b/modules/somafm/backend.py new file mode 100644 index 00000000..51020491 --- /dev/null +++ b/modules/somafm/backend.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2013 Roger Philibert +# +# 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 . + + +import lxml.etree +from weboob.capabilities.radio import ICapRadio, Radio, Stream, Emission +from weboob.capabilities.collection import ICapCollection +from weboob.tools.backend import BaseBackend +from weboob.tools.browser import StandardBrowser +from weboob.tools.misc import to_unicode +from weboob.tools.parsers.iparser import IParser + + +__all__ = ['SomaFMBackend'] + + +class LxmlParser(IParser): + def parse(self, data, encoding=None): + return lxml.etree.fromstring(data.read()) + + +class SomaFMBackend(BaseBackend, ICapRadio, ICapCollection): + NAME = 'somafm' + MAINTAINER = u'Roger Philibert' + EMAIL = 'roger.philibert@gmail.com' + VERSION = '0.h' + DESCRIPTION = u'SomaFM web radio' + LICENSE = 'AGPLv3+' + BROWSER = StandardBrowser + + QUALITIES = ['fast', 'slow', 'highest'] + + ALLINFO = 'http://api.somafm.com/channels.xml' + + def create_default_browser(self): + return self.create_browser(parser=LxmlParser()) + + def _parse_current(self, data): + current = data.split(' - ') + if len(current) == 2: + return current + else: + return ('Unknown', 'Unknown') + + def _fetch_radio_list(self): + radios = [] + + document = self.browser.location(self.ALLINFO) + for channel in document.iter('channel'): + radio = Radio(channel.get('id')) + radio.title = channel.findtext('title') + radio.description = channel.findtext('description') + + current_data = channel.findtext('lastPlaying') + current = Emission(0) + current.artist, current.title = self._parse_current(current_data) + radio.current = current + + radio.streams = [] + stream_id = 0 + for subtag in channel: + if subtag.tag.endswith('pls'): + stream = Stream(stream_id) + stream.title = '%s/%s' % (subtag.tag.replace('pls', ''), subtag.get('format')) + stream.url = subtag.text + radio.streams.append(stream) + stream_id += 1 + + radios.append(radio) + + return radios + + def iter_radios_search(self, pattern): + radios = self._fetch_radio_list() + + pattern = pattern.lower() + for radio in radios: + if pattern in radio.title.lower() or pattern in radio.description.lower(): + yield radio + + def iter_resources(self, objs, split_path): + radios = self._fetch_radio_list() + + if Radio in objs: + self._restrict_level(split_path) + + for radio in radios: + yield radio + + def get_radio(self, radio_id): + radios = self._fetch_radio_list() + for radio in radios: + if radio_id == radio.id: + return radio + + def fill_radio(self, radio, fields): + if 'current' in fields: + if not radio.current: + radio.current = Emission(0) + radio.current.artist, radio.current.title = self.get_current(radio.id) + return radio + + #OBJECTS = {Radio: fill_radio} + diff --git a/modules/somafm/favicon.png b/modules/somafm/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..5ecbeda9f67aa94ddb4f4099cc74fbeb0c8ff5ce GIT binary patch literal 3704 zcmV-;4u|oHP)e zSad^gZEa<4bO1wgWnpw>WFU8GbZ8()Nlj2!fese{01eao2=Ky_U_u=mv`6mo;&x^KhC+HyW`nq*H{%K`;)F_ z=01M+cfRMG@Av%9!17og%VT*gkL9sEmdEnAY^(wv1oi+q!2bvyd(?(i^BHM0IF|&z z5A6EkKm8Q%&U^WJHNoozkAl}|2IB*vJoJUmtLio1m(TbecjN8+G%wjE4}ATv03HXv z36vHBU=?uup+_D`^VM(jqYFMb4$Mx$*;A0a2(BwnSy5q?_F)TvEu#ed1zY>4rz=0;<7XVf$^mo}`e3;y{E$~jBh5!9ecP;}v~3T#h6TQ0`~Nt++RF5m~icmTlX z9{u4HeCvFnmRM&`!=e3RJU}!CH{SxceG;0};>o78q(M^}Moz*npMxWBf?XECOanYu zxTBDT3*(TTfxAC@34{q34phX$L0A`kIARM3wl5wx00-!?px=OqQOn^CQMe}wryXcA za$yJpTPh+4aqDeGd3Z0KomFP1|6y0)CKnMlEN3U&s%ja~1UT zK{5rVDYs6I!M;5(Z~&?m@osB}7->de;w;Qg!}KJ){Br?p$ETq=&8!DA%Bx0Qh?o4i zY(1r5rA_EC1q9cJxB=HiVTGxh1Focf#TGZz6c$?A;S+xhnLZ)5BX39!pC1va zD;I^>@;QkR5R&X#EfQGOv`M$anrmV7l<-5jC}5OJ6nx214+-g36PiQfYy#^oS^Py? zQX^pbI3j&x&ZVTq&=e|bY6OHh<3Zk&x&Y6=9Dp059`EGr%PWmo95UBIORMDlzL#M3 z0ytF|Q)#p;NiNrgQb7!?R_0(yG9`6_k33r`gI&R4F75?iv^)$zR|J|(7;z+*OTMT> z$%lise8vp9FKs~17o#hFjYP8^tb7W&baBC3aI7g4Ryxy-^4< zk&^StCP=lw%4=k&=v1X_Zca;@^lyf~O^|LCU_6(*4!uOwkR0xg!1}1j-pUAMqOiu2 zYbHX3t1alZMO@dpMe# zfqq+B*L%Kv`mo@Z+1LF*!4m~=^|@FOQFrX%V5*a6fA;)>+u_R^B7gbtBTRR6^Y-LL zM)M`Ao^FM$sJy{2LQEC<9<0*$q_nlRkuMVPO%V1#%ZAho`i z^rnw9(cHlkKY422hR7d$OKf@kp^qhZG>0-62)_hJu;H=+yKi_7XZ?NwFq9#x{j_WJ zXx3aS0u#WP9)*tz5XKUWPXPVE?Z8%`7f9>nyaF%?90A@2M*4=S{$2nulpzA#2z&7bmQr{1N zn95!+&|2sHGH@I?4Wxi|>hX%6Kdh2iUcmS^O{*T2)rx{P2%H_tFs=YwI(JgR_H{Q` zQU$79DOigEpc7cF-@^cu)X0C=YwI;#O29DiI|aaLV7#gG-VN*kKCZugRgxiKKX4Ej z(dgTt?`YDw?}?!dqkY5JR~rCJJzt;80T5Hbd%#&WAf?x4H4QHFjC#Hu_#|*2 zuv614ssLn^bo(_rE-C;U^}a;#zbG&RoB>_|UK`4Auy1(bv|F+a>I-A@)DS5(yr`i# zqao`9-aN*)sHE-$?$i{EhCq$zx|%fdY;{7q!FpF}`eZZ(6GIvH_6<|KS^yZKrfnmF zyk7reIs=>sP5`68>^wPXX&B!Q+@*ORZ7_dCfnObJ?V0neAqDg)H&lUHjRf~=7=BcYC zgUJRd5Xe`XhG(}fpj}yiOi8NT0DxY-w<%Gy^v% z5b>)~3I|o4c_qDrdCKb6x`N7AqmBc=1Wqa-H1NEm*I!oov+LS|mJ(yPuKUUXP{vNw zEw+9^9<{&o3rdFXsTx!p%oh)Zc)e%G_3x^~nhuSkpakp%o(A4((BKS|sbwDJS`r6D zbiv66_XP$Q)zEy11ox>il?L-!I-l79fQoX*37t3HU`<8cZVv#PLqOFxkt2(ZSZfgk zn5>&Dr~;3u7pBzX?uFF~&RT>Z?b=`93Y5-u(v*0pt40sMW zrRVbRvxy1ji_SxC!Q7QlZW|3EuN+ECqrp8vT7KXN2z0m+Pq^xd(Y|3um)L=7RG>I& zbR^VZZ8U66xj=pV2J?G$>Tty>;D;9GH$d3{qDpRy8LoOa2w}fY1q%AUf^LYs2G7sx zbx-Fh)ZORlxxxZcx@Z7`1<%y6asW(u30YlPN^q-{hUzZ%!i0DSC<+kRhcQR zJkJB?^t>Os=bRqp51In564Dvnb_UWCcw%OOP+bUsL%N6XwP2b6-ceo8D(L&QzP~xt zV;BV9P_-#22kqB&_2_)3_1x(ODs)8v%qrU-P)~R1xufc({9>)|8^#~XFr&|=v|i{^ zPaf6Zc{OgYs?U46A)3+p;J50OidK#<>ND5sYffk_ac;5BF&5Dd4>m8GbTJon!LMso zwxn*%P=>hf>)W+j%V}gx1`Q3pXEm@!JzNfjc0s|6Xh|K+g#tCMGVFd}0O}UJLc_77 zAdCaWD``b(M<@~2_xjek-V-jG%77DoU#Pl_hMFWxa)_~n1uwXm<*_`L$MX0nj{gJJ W#~C-{M07U*0000. + + +from weboob.tools.test import BackendTest +from weboob.capabilities.radio import Radio + + +class SomaFMTest(BackendTest): + BACKEND = 'somafm' + + def test_difm(self): + ls = list(self.backend.iter_resources((Radio, ), [])) + self.assertTrue(len(ls) > 0) + + search = list(self.backend.iter_radios_search('doom')) + self.assertTrue(len(search) > 0) + self.assertTrue(len(search) < len(ls)) + + radio = self.backend.get_radio('doomed') + 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) +